ruby-hue 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|