ruby-hue 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.gitmodules +6 -0
- data/Gemfile +9 -0
- data/LICENSE.md +31 -0
- data/README.md +36 -0
- data/Rakefile +1 -0
- data/hue.gemspec +19 -0
- data/lib/hue.rb +327 -0
- data/lib/hue/version.rb +3 -0
- metadata +56 -0
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
The Azure License
|
2
|
+
|
3
|
+
Copyright (c) 2012 Kenneth Ballenegger
|
4
|
+
|
5
|
+
Attribute to Kenneth Ballenegger - kswizz.com
|
6
|
+
|
7
|
+
You (the licensee) are hereby granted permission, free of charge,
|
8
|
+
to deal in this software or source code (this "Software") without
|
9
|
+
restriction, including without limitation the rights to use, copy,
|
10
|
+
modify, merge, publish, distribute, and/or sublicense this
|
11
|
+
Software, subject to the following conditions:
|
12
|
+
|
13
|
+
You must give attribution to the party mentioned above, by name and
|
14
|
+
by hyperlink, in the about box, credits document and/or
|
15
|
+
documentation of any derivative work using a substantial portion of
|
16
|
+
this Software.
|
17
|
+
|
18
|
+
You may not use the name of the copyright holder(s) to endorse or
|
19
|
+
promote products derived from this Software without specific prior
|
20
|
+
written permission.
|
21
|
+
|
22
|
+
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
23
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
24
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
25
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
26
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
27
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
28
|
+
CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN THIS
|
29
|
+
SOFTWARE.
|
30
|
+
|
31
|
+
http://license.azuretalon.com/
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Hue
|
2
|
+
|
3
|
+
`ruby-hue` is a simple Ruby library and binary for manipulating the Philips Hue
|
4
|
+
lighting system.
|
5
|
+
|
6
|
+
## Examples
|
7
|
+
|
8
|
+
(In a `pry` shell... for awesomeness)
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
require 'hue'
|
12
|
+
h = Hue::Hue.new # make use of auto-discovery
|
13
|
+
|
14
|
+
h.poll_state # fetch and print system status
|
15
|
+
|
16
|
+
h.set_bright_color(1, Color::RGB::Red)
|
17
|
+
|
18
|
+
h.all_lights.write bri: 255, hue: 40000, sat: 200
|
19
|
+
|
20
|
+
# alternate between blue and red
|
21
|
+
(1..1.0/0).each do |n|
|
22
|
+
h.all_lights.write hue: n.even? ? 0 : 248 * 182, transitiontime: 1
|
23
|
+
sleep 0.15
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
## Documentation
|
28
|
+
|
29
|
+
It is highly recommended that you read the inline comments in `lib/hue.rb`. The
|
30
|
+
module is very thoroughly documented.
|
31
|
+
|
32
|
+
## Credit
|
33
|
+
|
34
|
+
Thanks to this link for figuring out most of the unofficial Hue REST API:
|
35
|
+
|
36
|
+
http://rsmck.co.uk/hue
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/hue.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hue/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'ruby-hue'
|
8
|
+
gem.version = Hue::VERSION
|
9
|
+
gem.authors = ['Kenneth Ballenegger']
|
10
|
+
gem.email = ['kenneth@ballenegger.com']
|
11
|
+
gem.description = %q{Control Philips Hue}
|
12
|
+
gem.summary = %q{Control Philips Hue}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
end
|
data/lib/hue.rb
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
|
2
|
+
require 'curb'
|
3
|
+
require 'upnp/ssdp'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'json'
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'color'
|
8
|
+
|
9
|
+
|
10
|
+
module Hue
|
11
|
+
|
12
|
+
# Hue is a class that can interact with and control a Philips Hue base station.
|
13
|
+
#
|
14
|
+
class Hue
|
15
|
+
|
16
|
+
# Hue.discover_ip is a convenience class method that will scan the network
|
17
|
+
# and attempt to find the Philips base station. It may take ~5s to execute.
|
18
|
+
#
|
19
|
+
def self.discover_ip
|
20
|
+
UPnP::SSDP.log = false # get rid of this pesky debug logging!
|
21
|
+
services = UPnP::SSDP.search('urn:schemas-upnp-org:device:basic:1').map {|s| s[:location] }
|
22
|
+
valid_location = services.find do |l|
|
23
|
+
xml = Curl.get(l).body_str
|
24
|
+
doc = Nokogiri::XML(xml)
|
25
|
+
name = doc.css('friendlyName').first
|
26
|
+
return false unless name
|
27
|
+
name.text =~ /^Philips hue/
|
28
|
+
end
|
29
|
+
raise 'no hue found on this network' unless valid_location
|
30
|
+
/(([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])/.match(valid_location)[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Hue can be initialized with a Hash of options.
|
35
|
+
#
|
36
|
+
# - opts[:ip] should contain the IP of the base station
|
37
|
+
# by default, Hue will use discover_ip to attempt to find it.
|
38
|
+
#
|
39
|
+
# - opts[:client] is a User-Agent-like string, describing this client.
|
40
|
+
# defaults to 'ruby-hue'
|
41
|
+
#
|
42
|
+
# - opts[:username] is a hex secret string used for authentication
|
43
|
+
# defaults to the SHA1 hex digest of the hostname
|
44
|
+
#
|
45
|
+
def initialize(opts = {})
|
46
|
+
@ip = opts[:ip] || self.class.discover_ip
|
47
|
+
@client = opts[:client] || 'ruby-hue'
|
48
|
+
@username = opts[:username] || Digest::SHA1.hexdigest(`hostname`.strip)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Hue#request is makes an authorized request to the Hue backend, relative to
|
53
|
+
# the url `http://@ip/api/@user`. Include any leading slashes.
|
54
|
+
#
|
55
|
+
# This method is mostly used internally, but made avaiable publicly for shits
|
56
|
+
# and giggles.
|
57
|
+
#
|
58
|
+
def request(method, path, body = {})
|
59
|
+
url = "http://#{@ip}/api/#{@username}#{path}"
|
60
|
+
_request(method, url, body)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Hue#authorize authorizes this client (@client + @username combination) with
|
65
|
+
# the base station forever. It's a one-time operation.
|
66
|
+
#
|
67
|
+
# #authorize requires manual interation: the method must be called, after
|
68
|
+
# which somebody must *physically* press the button on the base station.
|
69
|
+
# Once that is done, the method must be called again and, this time,
|
70
|
+
# a success confirmation message will be returned.
|
71
|
+
#
|
72
|
+
def authorize
|
73
|
+
_request(:post, "http://#{@ip}/api/", devicetype: @client, username: @username)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Hue#poll_state returns the entire state of the system.
|
78
|
+
#
|
79
|
+
def poll_state
|
80
|
+
state = request(:get, '/')
|
81
|
+
raise unless state['lights'] # poor man's way of checking for success
|
82
|
+
@state = state
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Hue#lights returns lights from the cached state, from the poll_state method
|
87
|
+
# above. The state of the light themselves is only as current as the last
|
88
|
+
# poll_state method call.
|
89
|
+
#
|
90
|
+
def lights
|
91
|
+
poll_state unless @state
|
92
|
+
@state['lights']
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Like Enum's `each`, Hue#each_light takes a block which is called for each
|
97
|
+
# light *id*. For example:
|
98
|
+
#
|
99
|
+
# hue.each_light do |l|
|
100
|
+
# hue.set_bright_color(l, Color::RGB::Blue)
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
def each_light
|
104
|
+
lights.each {|k,v| yield k }
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Hue#all_lights is awesome. It returns a `HueAllLightsProxy` object, which
|
110
|
+
# is a simple object that will forward any method calls it gets to this
|
111
|
+
# instance of Hue, once for each light, inserting the light id as the first
|
112
|
+
# argument.
|
113
|
+
#
|
114
|
+
# Imagine we had the following variables:
|
115
|
+
#
|
116
|
+
# hue = Hue.new
|
117
|
+
# proxy = hue.all_lights
|
118
|
+
#
|
119
|
+
# The following method call:
|
120
|
+
#
|
121
|
+
# proxy.write hue: 0, bri: 200
|
122
|
+
#
|
123
|
+
# Would essentially be translated to these three method calls (for 3 lights):
|
124
|
+
#
|
125
|
+
# hue.write 1, hue: 0, bri: 200
|
126
|
+
# hue.write 2, hue: 0, bri: 200
|
127
|
+
# hue.write 3, hue: 0, bri: 200
|
128
|
+
#
|
129
|
+
# ---
|
130
|
+
#
|
131
|
+
# Typically, you would simply use this inline, like this:
|
132
|
+
#
|
133
|
+
# hue.all_lights.set_bright_color(Color::RGB::Blue)
|
134
|
+
#
|
135
|
+
def all_lights
|
136
|
+
HueAllLightsProxy.new(self)
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
# Hue#write is the meat of what Hue is all about. This is the method that
|
141
|
+
# writes a state change to a Hue bulb.
|
142
|
+
#
|
143
|
+
# In order to avoid being rate limited at the HTTP api level, this method
|
144
|
+
# implements its own rate limiting in which it will block and wait for enough
|
145
|
+
# time to elapse when messages are fired too quickly.
|
146
|
+
#
|
147
|
+
# State is an object which typically contains a combination of these keys:
|
148
|
+
#
|
149
|
+
# :hue => this is a hue value in the range (0..65535)
|
150
|
+
# can be derived from (degrees * 182)
|
151
|
+
# :bri => "brightness" in the range (0..255)
|
152
|
+
# :sat => "saturation" in the range (0..255)
|
153
|
+
# :alert => when set, triggers alert behavior. options:
|
154
|
+
# :select
|
155
|
+
# triggers a flash to the given color, instead of
|
156
|
+
# a permanent change, when set to `true`
|
157
|
+
# :lselect
|
158
|
+
# same as above, except this is a constant flashing,
|
159
|
+
# insead of a one time trigger
|
160
|
+
# :transitiontime => an Integer representing the "transition time," ie. the
|
161
|
+
# amount of time over which the color fade will occur.
|
162
|
+
# this is measured in tenths of seconds. a value of
|
163
|
+
# 0 means a hard switch, no fade. defaulys to ~5-ish
|
164
|
+
#
|
165
|
+
def write(light, state)
|
166
|
+
wait_for_rate_limit
|
167
|
+
request(:put, "/lights/#{light}/state", state)
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
# Hue#off turns off the specified light.
|
172
|
+
#
|
173
|
+
def off(light)
|
174
|
+
write(light, on: false)
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# Hue#on turns on the specified light.
|
179
|
+
#
|
180
|
+
def on(light)
|
181
|
+
write(light, on: true)
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
# Hue#set_color sets a light to a specified color.
|
186
|
+
#
|
187
|
+
# set_color expects color to be a color object from the `color` gem, or
|
188
|
+
# something that implements its interface:
|
189
|
+
#
|
190
|
+
# color must implement to_hsl, which must return an object that implements
|
191
|
+
# .h, .l, and .s. These methods are expected to return floats in (0.0..1.1)
|
192
|
+
#
|
193
|
+
def set_color(light, color, opts={})
|
194
|
+
hsl = color.to_hsl
|
195
|
+
opts = opts.merge(bri: (hsl.l * 255).to_i,
|
196
|
+
sat: (hsl.s * 255).to_i,
|
197
|
+
hue: (hsl.h * 360 * 182).to_i)
|
198
|
+
write(light, opts)
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# Hue#set_bright_color is often more useful than its `set_color`
|
203
|
+
# counterpart, because when converting RGB colors to HSL, a color might not
|
204
|
+
# be expected to have full brightness. But with a known hue & saturation,
|
205
|
+
# it's often desirable to have full brightness. This method uses `bri: 255`
|
206
|
+
#
|
207
|
+
def set_bright_color(light, color, opts={})
|
208
|
+
color = color.to_hsl
|
209
|
+
color.l = 1
|
210
|
+
set_color(light, color, opts)
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
# Hue#preset returns a HuePresetsProxy object, which is meant to be used
|
215
|
+
# much like Hue#all_lights. For example:
|
216
|
+
#
|
217
|
+
# hue.preset.cycle_thru_colors
|
218
|
+
#
|
219
|
+
def preset
|
220
|
+
HuePresetsProxy.new(self)
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def wait_for_rate_limit
|
231
|
+
@last_request_times ||= []
|
232
|
+
while @last_request_times.count > 25
|
233
|
+
@last_request_times.select! {|t| Time.now - t < 1 }
|
234
|
+
sleep 0.1
|
235
|
+
end
|
236
|
+
@last_request_times << Time.now
|
237
|
+
end
|
238
|
+
|
239
|
+
def _request(method, url, body = {})
|
240
|
+
body_str = body.to_json
|
241
|
+
case method
|
242
|
+
when :get
|
243
|
+
r = Curl.get(url)
|
244
|
+
when :post
|
245
|
+
r = Curl.post(url, body.to_json)
|
246
|
+
when :put
|
247
|
+
r = Curl.put(url, body.to_json)
|
248
|
+
else
|
249
|
+
raise
|
250
|
+
end
|
251
|
+
JSON.parse(r.body_str)
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
# Presets is a module which contains neat helper functions that are part of
|
259
|
+
# the basic interface for interaction with Hue, but which uses Hue to create
|
260
|
+
# some very neat effects.
|
261
|
+
#
|
262
|
+
module Presets
|
263
|
+
|
264
|
+
def self.cycle_thru_color_arr(hue, colors, sleep_between_steps = 1)
|
265
|
+
colors = colors.dup
|
266
|
+
loop do
|
267
|
+
hue.each_light {|l| hue.set_bright_color(l, colors.first)}
|
268
|
+
colors << colors.shift
|
269
|
+
sleep sleep_between_steps
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.cycle_thru_colors(hue, sleep_between_steps = 1)
|
274
|
+
(0..65535).step(5000).each do |n|
|
275
|
+
hue.all_lights.write(hue: n); sleep sleep_between_steps
|
276
|
+
end while true
|
277
|
+
end
|
278
|
+
|
279
|
+
# --- even more specific
|
280
|
+
|
281
|
+
def self.police_lights(hue)
|
282
|
+
colors = [Color::RGB::Blue, Color::RGB::Red]
|
283
|
+
loop do
|
284
|
+
hue.all_lights.set_bright_color(colors.first, transitiontime: 0)
|
285
|
+
colors << colors.shift
|
286
|
+
sleep 0.1
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.strobe(hue)
|
291
|
+
hue.all_lights.write bri: 0
|
292
|
+
loop do
|
293
|
+
hue.all_lights.write(bri: 255,
|
294
|
+
alert: :select,
|
295
|
+
transitiontime: 0)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
|
302
|
+
|
303
|
+
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
class HueAllLightsProxy
|
308
|
+
def method_missing(method, *args)
|
309
|
+
@h.each_light {|l| @h.send(method, l, *args) }
|
310
|
+
end
|
311
|
+
|
312
|
+
def initialize(hue)
|
313
|
+
@h = hue
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class HuePresetsProxy
|
318
|
+
def method_missing(method, *args)
|
319
|
+
Presets.send(method, @h, *args)
|
320
|
+
end
|
321
|
+
|
322
|
+
def initialize(hue)
|
323
|
+
@h = hue
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
data/lib/hue/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-hue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kenneth Ballenegger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-15 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Control Philips Hue
|
15
|
+
email:
|
16
|
+
- kenneth@ballenegger.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- .gitmodules
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- LICENSE.md
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- hue.gemspec
|
29
|
+
- lib/hue.rb
|
30
|
+
- lib/hue/version.rb
|
31
|
+
homepage: ''
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.24
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Control Philips Hue
|
55
|
+
test_files: []
|
56
|
+
has_rdoc:
|