ruby-hue 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,6 @@
1
+ [submodule "upnp"]
2
+ path = upnp
3
+ url = https://github.com/turboladen/upnp.git
4
+ [submodule "httpi"]
5
+ path = httpi
6
+ url = https://github.com/turboladen/httpi.git
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem 'curb'
5
+ gem 'nokogiri'
6
+ gem 'color'
7
+
8
+ gem 'upnp', path: './upnp'
9
+
@@ -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/
@@ -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
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ class Hue
2
+ VERSION = '0.0.1'
3
+ end
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: