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 +17 -0
- data/Gemfile +7 -0
- data/README.md +66 -0
- data/Rakefile +32 -0
- data/examples/console.rb +54 -0
- data/lib/ruhue.rb +108 -0
- data/lib/ruhue/client.rb +61 -0
- data/lib/ruhue/response.rb +23 -0
- data/lib/ruhue/version.rb +4 -0
- data/ruhue.gemspec +25 -0
- metadata +106 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
data/examples/console.rb
ADDED
@@ -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'
|
data/lib/ruhue/client.rb
ADDED
@@ -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
|
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:
|