ruhue 0.1.0

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 ADDED
@@ -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
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'pry'
6
+ gem 'yard'
7
+ gem 'redcarpet'
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # Ruhue
2
+
3
+ So far, mere documentations of my findings when sniffing the Hue.
4
+
5
+ There is a mailing list, dedicated to discussions and questions about hacking the
6
+ Philips Hue and related protocols.
7
+
8
+ - Mailing list web interface: <https://groups.google.com/d/forum/hue-hackers>
9
+ - Mailing list e-mail address: <hue-hackers@googlegroups.com>
10
+
11
+ Other link resources:
12
+
13
+ - [Hack the Hue](http://rsmck.co.uk/hue)
14
+ - [A Day with Philips Hue](http://www.nerdblog.com/2012/10/a-day-with-philips-hue.html?showComment=1352172383498)
15
+
16
+ ## Console
17
+
18
+ There is a console script in this repository, written by @Burgestrand as the
19
+ documentation effort travels further. It is written in Ruby, and only supports
20
+ Ruby 1.9.x and newer. You may start the console with the following:
21
+
22
+ 1. Install bundler: `gem install bundler`
23
+ 2. Install console script dependencies: `bundle install`
24
+ 3. Run the console script: `ruby console.rb`
25
+
26
+ You’ll be dropped into a pry prompt (similar to IRB), with access to the following
27
+ local variables:
28
+
29
+ - hue — a Hue instance, documented in `lib/hue.rb`
30
+ - client — a Hue::Client, documented in `lib/hue/client.rb`
31
+
32
+ Once the Hue API exploration adventure starts slowing down, the scripts will be
33
+ turned into a ruby gem and tested with rspec.
34
+
35
+ ## API
36
+
37
+ The API reference documentation has moved to the [hue-api][] repository. It is
38
+ hosted on <http://burgestrand.github.com/hue-api/> using GitHub Pages. Contributions
39
+ are very welcome, and if you’d like commit access to the hue-api repository, just ask!
40
+
41
+ [hue-api]: https://github.com/Burgestrand/hue-api
42
+
43
+ ## Gem License
44
+
45
+ Copyright (c) 2012 Kim Burgestrand
46
+
47
+ MIT License
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ "Software"), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
63
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
64
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
65
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
66
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require "bundler/gem_tasks"
3
+ rescue LoadError
4
+ # Not everybody needs bundler tasks.
5
+ end
6
+
7
+ begin
8
+ require 'yard'
9
+ YARD::Rake::YardocTask.new('yard:doc') do |task|
10
+ task.options = ['--no-stats']
11
+ end
12
+
13
+ task 'yard:stats' do
14
+ YARD::CLI::Stats.run('--list-undoc')
15
+ end
16
+
17
+ task :yard => ['yard:doc', 'yard:stats']
18
+ rescue LoadError
19
+ puts "WARN: YARD not available. You may install documentation dependencies via bundler."
20
+ end
21
+
22
+ desc "Run an interactive console with Ruhue loaded"
23
+ task :console do
24
+ exec "irb", "-Ilib", "-rruhue"
25
+ end
26
+
27
+ require 'rspec/core/rake_task'
28
+ RSpec::Core::RakeTask.new do |spec|
29
+ spec.ruby_opts = %w[-W]
30
+ end
31
+
32
+ task :default => :spec
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ require "bundler/setup"
4
+ require "pry"
5
+ require "ruhue"
6
+
7
+ def prompt(query)
8
+ print(query)
9
+ gets.chomp.tap { puts }
10
+ end
11
+
12
+ puts <<-HELLO
13
+ Hi there!
14
+
15
+ This is an interpreter to aid experimenting with the Hue. It’s written in Ruby
16
+ and makes API calls to the Hue hub a little more pleasant. You’re expected to
17
+ know Ruby in order to use this.
18
+
19
+ I’ll first ask you for your Hue hub username. If you do not have one, just make
20
+ one up and I’ll register an application for you on the Hue hub. Use the same
21
+ username every time, or you’ll have a lot of unused applications registered to
22
+ your Hue after a little while.
23
+
24
+ First off, I’ll see if I can find your Hue. Give me five seconds at most.
25
+ HELLO
26
+
27
+ hue = Ruhue.discover
28
+ puts
29
+ puts "Hue discovered at #{hue.host}!"
30
+ puts
31
+
32
+ username = prompt("Now, your username please (10-40 characters, 0-9, a-z, A-Z): ")
33
+ client = Ruhue::Client.new(hue, username)
34
+
35
+ puts "Hi #{client.username}!"
36
+
37
+ if client.registered?
38
+ puts "It appears you’re already registered with the Hub. Play away!"
39
+ elsif client.register("ruhue")
40
+ puts "A new application has been registered with the Hub as #{client.username}. Play away!"
41
+ end
42
+
43
+ # Blinks all your lights!
44
+ #
45
+ # num_lights = 3
46
+ # on = true
47
+ # 10_000.times do |i|
48
+ # sleep(0.04)
49
+ # light = (i%num_lights) + 1
50
+ # client.put("/lights/#{light}/state", on: on)
51
+ # on = ! on if light == num_lights
52
+ # end
53
+
54
+ binding.pry
data/lib/ruhue.rb ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'httpi'
5
+ require 'nokogiri'
6
+ require 'socket'
7
+ require 'timeout'
8
+ require 'json'
9
+
10
+ class Ruhue
11
+ TimeoutError = Class.new(TimeoutError)
12
+ APIError = Class.new(StandardError)
13
+
14
+ class << self
15
+ # Search for a Hue hub, with configurable timeout.
16
+ #
17
+ # This sends out a broadcast packet with UDP, and waits for
18
+ # a response from the Hue hub.
19
+ #
20
+ # @param [Integer] timeout seconds until giving up
21
+ # @raise [Ruhue::TimeoutError] in case timeout is reached
22
+ # @return [Hub]
23
+ def discover(timeout = 5)
24
+ socket = UDPSocket.new(Socket::AF_INET)
25
+ payload = []
26
+ payload << "M-SEARCH * HTTP/1.1"
27
+ payload << "HOST: 239.255.255.250:1900"
28
+ payload << "MAN: ssdp:discover"
29
+ payload << "MX: 10"
30
+ payload << "ST: ssdp:all"
31
+ socket.send(payload.join("\n"), 0, "239.255.255.250", 1900)
32
+
33
+ Timeout.timeout(timeout, Ruhue::TimeoutError) do
34
+ loop do
35
+ message, (_, _, hue_ip, _) = socket.recvfrom(1024)
36
+ # TODO: improve this. How do we know it’s a Hue hub?
37
+ return new(hue_ip) if message =~ /description\.xml/
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # @example
44
+ # hue = Ruhue.new("192.168.0.21")
45
+ #
46
+ # @param [String] host address to the Hue hub.
47
+ def initialize(host)
48
+ @host = host.to_str
49
+ end
50
+
51
+ # @return [String] hue host
52
+ attr_reader :host
53
+
54
+ # @param [String] path
55
+ # @return [String] full url
56
+ def url(path)
57
+ "http://#{host}/#{path.to_s.sub(/\A\//, "")}"
58
+ end
59
+
60
+ # GET a path of the Hue.
61
+ #
62
+ # @param [String] path
63
+ # @return [Ruhue::Response]
64
+ def get(path)
65
+ request(:get, path)
66
+ end
67
+
68
+ # POST a payload to the Hue.
69
+ #
70
+ # @param [String] path
71
+ # @param data json-serializable
72
+ # @return [Ruhue::Response]
73
+ def post(path, data)
74
+ request(:post, path, JSON.dump(data))
75
+ end
76
+
77
+ # PUT a payload to the Hue.
78
+ #
79
+ # @param [String] path
80
+ # @param data json-serializable
81
+ # @return [Ruhue::Response]
82
+ def put(path, data)
83
+ request(:put, path, JSON.dump(data))
84
+ end
85
+
86
+ # DELETE a resource.
87
+ #
88
+ # @param [String] path
89
+ # @return [Ruhue::Response]
90
+ def delete(path)
91
+ request(:delete, path)
92
+ end
93
+
94
+ # @return [Nokogiri::XML] Hue device description
95
+ def description
96
+ Nokogiri::XML(get("/description.xml").body)
97
+ end
98
+
99
+ protected
100
+
101
+ def request(method, path, *args)
102
+ response = HTTPI.send(method, url(path), *args)
103
+ Ruhue::Response.new(response)
104
+ end
105
+ end
106
+
107
+ require 'ruhue/response'
108
+ require 'ruhue/client'
@@ -0,0 +1,61 @@
1
+ class Ruhue::Client
2
+ class << self
3
+ def valid_username?(name)
4
+ name =~ /\A[0-9a-zA-Z]{10,40}\z/
5
+ end
6
+ end
7
+
8
+ # Create a Hue client. You’ll need a Hue hub
9
+ # username for this, created via a POST to the
10
+ # /api endpoint. See README for more information.
11
+ #
12
+ # @param [Ruhue] hue
13
+ # @param [String] username
14
+ def initialize(hue, username)
15
+ unless self.class.valid_username?(username)
16
+ raise ArgumentError, "invalid username, must be length 10-40, only numbers and letters"
17
+ end
18
+
19
+ @hue = hue
20
+ @username = username
21
+ end
22
+
23
+ # @return [Ruhue]
24
+ attr_reader :hue
25
+
26
+ # @return [String]
27
+ attr_reader :username
28
+
29
+ # Register a given username with the Hue hub.
30
+ #
31
+ # @param [String] device_type used as device name
32
+ # @return [Ruhue::Client] if successful
33
+ # @raise [APIError] if failed
34
+ def register(device_type)
35
+ response = hue.post("/api", username: username, devicetype: device_type)
36
+ tap { raise Ruhue::APIError, response.error_messages.join(", ") if response.error? }
37
+ end
38
+
39
+ # @return [Boolean] true if username is registered.
40
+ def registered?
41
+ not get("/").error?
42
+ end
43
+
44
+ def get(path)
45
+ hue.get(url(path))
46
+ end
47
+
48
+ def post(path, data)
49
+ hue.post(url(path), data)
50
+ end
51
+
52
+ def put(path, data)
53
+ hue.put(url(path), data)
54
+ end
55
+
56
+ protected
57
+
58
+ def url(path)
59
+ "/api/#{username}/#{path.sub(/\A\//, "")}"
60
+ end
61
+ end
@@ -0,0 +1,23 @@
1
+ class Ruhue::Response
2
+ # @param [HTTPI::Response] response
3
+ def initialize(response)
4
+ @response = response
5
+ @data = JSON.load(response.body)
6
+ end
7
+
8
+ # @return [String] body
9
+ attr_reader :response
10
+
11
+ # @return [Array, Hash] data
12
+ attr_reader :data
13
+
14
+ # @return [Boolean] true if the response is an error.
15
+ def error?
16
+ data.is_a?(Array) and data.any? { |hash| hash.has_key?("error") }
17
+ end
18
+
19
+ # @return [Array<String>, nil] array of error messages and their address, nil if no error.
20
+ def error_messages
21
+ data.map { |hash| "#{hash["error"]["address"]}: #{hash["error"]["description"]}" } if error?
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ class Ruhue
2
+ # @see http://semver.org/
3
+ VERSION = "0.1.0"
4
+ end
data/ruhue.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruhue/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "ruhue"
8
+ gem.summary = %q{API client for interacting with the Philips Hue Hub HTTP API.}
9
+
10
+ gem.homepage = "https://github.com/Burgestrand/ruhue"
11
+ gem.authors = ["Kim Burgestrand"]
12
+ gem.email = ["kim@burgestrand.se"]
13
+ gem.license = "MIT License"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.version = Ruhue::VERSION
21
+
22
+ gem.add_dependency 'httpi'
23
+ gem.add_dependency 'nokogiri'
24
+ gem.add_development_dependency 'rspec'
25
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruhue
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kim Burgestrand
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httpi
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description:
63
+ email:
64
+ - kim@burgestrand.se
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - Gemfile.lock
72
+ - README.md
73
+ - Rakefile
74
+ - examples/console.rb
75
+ - lib/ruhue.rb
76
+ - lib/ruhue/client.rb
77
+ - lib/ruhue/response.rb
78
+ - lib/ruhue/version.rb
79
+ - ruhue.gemspec
80
+ homepage: https://github.com/Burgestrand/ruhue
81
+ licenses:
82
+ - MIT License
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.24
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: API client for interacting with the Philips Hue Hub HTTP API.
105
+ test_files: []
106
+ has_rdoc: