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.
@@ -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: