hue-lib 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.md +33 -28
- data/hue-lib.gemspec +2 -2
- data/lib/hue/bridge.rb +33 -56
- data/lib/hue/config/abstract.rb +73 -0
- data/lib/hue/config/application.rb +55 -0
- data/lib/hue/config/bridge.rb +45 -0
- data/lib/hue.rb +93 -12
- data/spec/config/applications.yml +4 -0
- data/spec/config/bridges.yml +2 -3
- data/spec/hue/bridge_spec.rb +20 -30
- data/spec/hue/bulb_spec.rb +2 -9
- data/spec/hue/config/abstract_spec.rb +35 -0
- data/spec/hue/config/application_spec.rb +61 -0
- data/spec/hue/config/bridge_spec.rb +35 -0
- data/spec/hue_spec.rb +70 -3
- data/spec/json/config/whitelist/application_uuid/delete_success.json +1 -0
- data/spec/json/{base.json → get_success.json} +0 -0
- data/spec/json/post_error.json +1 -0
- data/spec/json/post_success.json +8 -0
- data/spec/json/put_success.json +1 -0
- data/spec/spec_helper.rb +132 -13
- metadata +16 -6
- data/lib/hue/config.rb +0 -94
- data/spec/hue/config_spec.rb +0 -65
data/.gitignore
ADDED
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
1
|
+
hue-lib
|
2
2
|
================
|
3
3
|
|
4
|
-
This is a
|
4
|
+
This is a Ruby library for controlling the [Philips Hue](http://www.meethue.com) lighting system.
|
5
|
+
The API has not yet been released, but there are [several](http://www.nerdblog.com/2012/10/a-day-with-philips-hue.html) [people](http://rsmck.co.uk/hue) working to figure it out.
|
5
6
|
|
6
7
|
# WARNING
|
7
8
|
All of this is very experimental and could permanently damage your awesome (but ridiculously expensive) lightbulbs. As such, exercise extreme caution.
|
@@ -9,30 +10,49 @@ All of this is very experimental and could permanently damage your awesome (but
|
|
9
10
|
## Getting Started
|
10
11
|
You can get a [great overview](http://rsmck.co.uk/hue) of the options and limitations of the lights from Ross McKillop.
|
11
12
|
|
12
|
-
You will need to find the IP address of your bridge unit and also generate a unique ID (UUID works great) for your controlling application and add them to the top of the `hue.rb` file.
|
13
|
-
|
14
|
-
You will need to use this information to register your app with the controller. This library does not do that at this time, so you will need to manually do that. I suggest following the *Registering Your Application* section of [Ross's overview](http://rsmck.co.uk/hue).
|
15
|
-
|
16
13
|
## Usage
|
17
|
-
To begin using, fire up the irb console
|
14
|
+
To begin using, fire up the irb console from the project root thus:
|
15
|
+
|
16
|
+
```
|
17
|
+
irb -I lib
|
18
|
+
```
|
18
19
|
|
19
20
|
```ruby
|
20
|
-
>>
|
21
|
+
>> require 'hue'
|
21
22
|
=> true
|
22
23
|
```
|
23
24
|
|
25
|
+
Start by registering your application. Press the button on the bridge and execute
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
>> Hue.register_default
|
29
|
+
=> #<Hue::Bridge:0x8b9d950 @application_id="4aa41fe737808af3559f3d22ca67a0ca", @base_uri="http://198.168.1.1/api">
|
30
|
+
```
|
31
|
+
|
32
|
+
This will create two config files in your ~/.hue-lib directory.
|
33
|
+
One for the bridges discovered on your network and one for the registered application.
|
34
|
+
|
35
|
+
You can fetch the default application thus:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
>> bridge = Hue.application
|
39
|
+
=> #<Hue::Bridge:0x8b9d950 @application_id="4aa41fe737808af3559f3d22ca67a0ca", @base_uri="http://198.168.1.1/api">
|
40
|
+
```
|
41
|
+
|
24
42
|
You can see all of the lights attached to your controller by querying the bridge.
|
25
43
|
|
26
44
|
```ruby
|
27
|
-
>>
|
28
|
-
=> {"1"=>"
|
45
|
+
>> bridge.lights
|
46
|
+
=> {"1"=>{"name"=>"Bedroom Overhead"}, "2"=>{"name"=>"Living Overhead"}, "3"=>{"name"=>"Standing Lamp"}, "4"=>{"name"=>"Living Cabinet"}}
|
47
|
+
>> bridge.light_names
|
48
|
+
=> "1. Bedroom Overhead\n2. Living Overhead\n3. Standing Lamp\n4. Living Cabinet"
|
29
49
|
```
|
30
50
|
|
31
51
|
If you know the ID number of a particular lamp, you can access it directly.
|
32
52
|
|
33
53
|
```ruby
|
34
|
-
>> b = Hue::Bulb.new(
|
35
|
-
=> #<Hue::Bulb:0x007fe35a3586b8 @
|
54
|
+
>> b = Hue::Bulb.new(bridge, 1)
|
55
|
+
=> #<Hue::Bulb:0x007fe35a3586b8 @bridge=#<Hue::Bridge:0x007fe35a358690 @id="1">>
|
36
56
|
|
37
57
|
# on/off
|
38
58
|
>> b.on?
|
@@ -62,27 +82,12 @@ If you know the ID number of a particular lamp, you can access it directly.
|
|
62
82
|
=> false
|
63
83
|
|
64
84
|
>> b.blink
|
65
|
-
=> nil
|
66
85
|
|
67
86
|
>> b.blinking?
|
68
87
|
=> true
|
69
88
|
|
70
|
-
>> b.
|
71
|
-
=> nil
|
89
|
+
>> b.solid
|
72
90
|
|
73
91
|
>> b.blinking?
|
74
92
|
=> false
|
75
93
|
```
|
76
|
-
|
77
|
-
## Experimental
|
78
|
-
There is an experimental mode that attempts to simulate a candle flicker. This defaults to only flickering 15 times as it's really not the way the bridge or bulbs were designed to work. Additionally, this operates on the main thread so you really can't do anything else while it's running.
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
>> b.candle
|
82
|
-
=> nil
|
83
|
-
```
|
84
|
-
|
85
|
-
The candle makes use of temporarily stashing the lamp's current settings before it starts and then restoring them upon completion. You can use this yourself with the `stash` and `restore` commands.
|
86
|
-
|
87
|
-
## Going Forward
|
88
|
-
There is still a lot of work to be done figuring out the various timer options of the hub, etc. Hopefully, the official API will be released in the near future and expose even more goodies that we're unaware of.
|
data/hue-lib.gemspec
CHANGED
@@ -3,8 +3,8 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "hue-lib"
|
6
|
-
s.version = '0.
|
7
|
-
s.authors = ["Birkir A. Barkarson", ""]
|
6
|
+
s.version = '0.6.0'
|
7
|
+
s.authors = ["Birkir A. Barkarson", "Aaron Hurley"]
|
8
8
|
s.email = ["birkirb@stoicviking.net"]
|
9
9
|
s.homepage = "https://github.com/birkirb/hue-lib"
|
10
10
|
s.summary = %q{Ruby library for controlling Phillips Hue light bridge.}
|
data/lib/hue/bridge.rb
CHANGED
@@ -1,52 +1,16 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
# require 'singleton'
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
4
3
|
|
5
4
|
module Hue
|
6
5
|
class Bridge
|
7
|
-
# include Singleton
|
8
|
-
|
9
|
-
# # Remove
|
10
|
-
# def self.method_missing(method, *args, &block)
|
11
|
-
# if args.empty?
|
12
|
-
# self.instance.send method
|
13
|
-
# else
|
14
|
-
# self.instance.send method, *args
|
15
|
-
# end
|
16
|
-
# end
|
17
|
-
|
18
|
-
# Move to APP class
|
19
|
-
def self.register(host = BASE)
|
20
|
-
# TODO: Look for default config.
|
21
|
-
puts "Please press the button on bridge before continuing."
|
22
|
-
puts "Once done, press Enter to continue."
|
23
|
-
gets
|
24
|
-
secret = Digest::MD5.hexdigest(UUID.generate) # one time UUID
|
25
|
-
puts "Registering app...(#{secret})"
|
26
|
-
config = Hue::Config.new(host, secret)
|
27
|
-
instance.create(
|
28
|
-
URI.parse(config.base_uri),
|
29
|
-
{"username" => config.base_uri, "devicetype" => Hue.device_type}
|
30
|
-
)
|
31
|
-
config.write
|
32
|
-
end
|
33
|
-
|
34
|
-
# Move to APP class
|
35
|
-
def self.remove
|
36
|
-
config = Config.default
|
37
|
-
instance.delete(
|
38
|
-
URI.parse(config.base_uri),
|
39
|
-
{"username" => config.identifier}
|
40
|
-
)
|
41
|
-
config.delete
|
42
|
-
end
|
43
6
|
|
44
7
|
public
|
45
8
|
|
46
|
-
attr_reader :
|
9
|
+
attr_reader :application_id, :bridge_uri
|
47
10
|
|
48
|
-
def initialize(
|
49
|
-
@
|
11
|
+
def initialize(application_id, bridge_uri)
|
12
|
+
@application_id = application_id
|
13
|
+
@bridge_uri = bridge_uri
|
50
14
|
end
|
51
15
|
|
52
16
|
def status
|
@@ -93,42 +57,55 @@ module Hue
|
|
93
57
|
update(uri('lights', id, 'state'), state)
|
94
58
|
end
|
95
59
|
|
60
|
+
def register
|
61
|
+
create(URI.parse(bridge_uri),
|
62
|
+
{"username" => application_id, "devicetype" => Hue.device_type})
|
63
|
+
end
|
64
|
+
|
65
|
+
def unregister
|
66
|
+
delete(uri('config', 'whitelist', application_id))
|
67
|
+
end
|
68
|
+
|
96
69
|
private
|
97
70
|
|
98
71
|
def uri(*args)
|
99
|
-
URI
|
72
|
+
URI.parse([bridge_uri, application_id, args].flatten.reject { |x| x.to_s.strip == '' }.join('/'))
|
100
73
|
end
|
101
74
|
|
102
75
|
def index(url)
|
103
|
-
# json = Net::HTTP.get(url)
|
104
|
-
# JSON.parse(json)
|
105
76
|
request = Net::HTTP::Get.new(url.request_uri, initheader = {'Content-Type' =>'application/json'})
|
106
|
-
|
107
|
-
display(response)
|
108
|
-
json = JSON.parse(response.body)
|
109
|
-
if json.is_a?(Array) && error = json.first['error']
|
110
|
-
raise Hue::API::Error.new(error)
|
111
|
-
else
|
112
|
-
json
|
113
|
-
end
|
77
|
+
parse_and_check_response(Net::HTTP.new(url.host, url.port).start { |http| http.request(request) })
|
114
78
|
end
|
115
79
|
|
116
80
|
def update(url, settings = {})
|
117
81
|
request = Net::HTTP::Put.new(url.request_uri, initheader = {'Content-Type' =>'application/json'})
|
118
82
|
request.body = settings.to_json
|
119
|
-
|
83
|
+
parse_and_check_response(Net::HTTP.new(url.host, url.port).start { |http| http.request(request) })
|
120
84
|
end
|
121
85
|
|
122
86
|
def delete(url, settings = {})
|
123
87
|
request = Net::HTTP::Delete.new(url.request_uri, initheader = {'Content-Type' =>'application/json'})
|
124
88
|
request.body = settings.to_json
|
125
|
-
|
89
|
+
parse_and_check_response(Net::HTTP.new(url.host, url.port).start{ |http| http.request(request) })
|
126
90
|
end
|
127
91
|
|
128
92
|
def create(url, settings = {})
|
129
93
|
request = Net::HTTP::Post.new(url.request_uri, initheader = {'Content-Type' =>'application/json'})
|
130
94
|
request.body = settings.to_json
|
131
|
-
|
95
|
+
parse_and_check_response(Net::HTTP.new(url.host, url.port).start { |http| http.request(request) })
|
96
|
+
end
|
97
|
+
|
98
|
+
def parse_and_check_response(response)
|
99
|
+
if display(response)
|
100
|
+
json = JSON.parse(response.body)
|
101
|
+
if json.is_a?(Array) && error = json.first['error']
|
102
|
+
raise Hue::API::Error.new(error)
|
103
|
+
else
|
104
|
+
json
|
105
|
+
end
|
106
|
+
else
|
107
|
+
raise Hue::Error.new("Unexpected response: #{response.code}, #{response.message}")
|
108
|
+
end
|
132
109
|
end
|
133
110
|
|
134
111
|
def display(response = nil)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Hue
|
5
|
+
module Config
|
6
|
+
class NotFound < Hue::Error; end;
|
7
|
+
|
8
|
+
class Abstract
|
9
|
+
|
10
|
+
public
|
11
|
+
|
12
|
+
attr_reader :path, :name
|
13
|
+
|
14
|
+
def initialize(name, path)
|
15
|
+
@path = path
|
16
|
+
@name = name
|
17
|
+
self.class.setup_file_path(self.path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(overwrite_existing_key = false)
|
21
|
+
yaml = YAML.load_file(self.path) rescue Hash.new
|
22
|
+
if yaml.key?(name) && !overwrite_existing_key
|
23
|
+
raise "Key named '#{name}' already exists in config file '#{self.path}'.\nPlease remove it before creating a new one with the same name."
|
24
|
+
else
|
25
|
+
add_self_to_yaml(yaml)
|
26
|
+
dump_yaml(yaml)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete
|
31
|
+
yaml = YAML.load_file(self.path) rescue Hash::New
|
32
|
+
|
33
|
+
if yaml.key?(name)
|
34
|
+
yaml.delete(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
dump_yaml(yaml)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(rhs)
|
41
|
+
lhs = self
|
42
|
+
|
43
|
+
lhs.class == rhs.class && lhs.name == rhs.name
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def add_self_to_yaml(yaml)
|
49
|
+
yaml[name] = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def dump_yaml(yaml)
|
53
|
+
File.open(self.path, 'w+' ) do |out|
|
54
|
+
YAML.dump(yaml, out)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.setup_file_path(path)
|
59
|
+
dir = File.dirname(path)
|
60
|
+
FileUtils.mkdir_p(dir) unless Dir.exists?(dir)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.read_file(config_file)
|
64
|
+
begin
|
65
|
+
yaml = YAML.load_file(config_file)
|
66
|
+
rescue => err
|
67
|
+
raise Error.new("Failed to read configuration file", err)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Hue
|
2
|
+
module Config
|
3
|
+
class Application < Abstract
|
4
|
+
|
5
|
+
STRING_BRIDGE_ID = 'bridge_id'
|
6
|
+
STRING_DEFAULT = 'default'
|
7
|
+
STRING_ID = 'id'
|
8
|
+
|
9
|
+
def self.file_path
|
10
|
+
File.join(ENV['HOME'], ".#{Hue.device_type}", 'applications.yml')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.default
|
14
|
+
named(STRING_DEFAULT)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.named(name)
|
18
|
+
yaml = read_file(file_path)
|
19
|
+
if named_yaml = yaml[name]
|
20
|
+
new(named_yaml[STRING_BRIDGE_ID], named_yaml[STRING_ID], name)
|
21
|
+
else
|
22
|
+
raise NotFound.new("Config named '#{name}' not found.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
public
|
27
|
+
|
28
|
+
attr_reader :bridge_id, :id, :name
|
29
|
+
|
30
|
+
def initialize(bridge_id, id, name = STRING_DEFAULT, path = self.class.file_path)
|
31
|
+
super(name, path)
|
32
|
+
@bridge_id = bridge_id
|
33
|
+
@id = id
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(rhs)
|
37
|
+
super(rhs) &&
|
38
|
+
self.bridge_id == rhs.bridge_id &&
|
39
|
+
self.id == rhs.id
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def add_self_to_yaml(yaml)
|
45
|
+
key = self.name.dup.force_encoding('ASCII') # Avoid binary encoded YAML
|
46
|
+
bridge = bridge_id.dup.force_encoding('ASCII')
|
47
|
+
yaml[key] = {
|
48
|
+
STRING_ID => id.force_encoding('ASCII'),
|
49
|
+
STRING_BRIDGE_ID => bridge,
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Hue
|
2
|
+
module Config
|
3
|
+
class Bridge < Abstract
|
4
|
+
|
5
|
+
def self.file_path
|
6
|
+
File.join(ENV['HOME'], ".#{Hue.device_type}", 'bridges.yml')
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find(id)
|
10
|
+
yaml = read_file(file_path)
|
11
|
+
entry = yaml.select { |k,v| k.to_s == id.to_s }
|
12
|
+
if entry.empty?
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
new(id, entry[id]['uri'])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
public
|
20
|
+
|
21
|
+
attr_accessor :uri
|
22
|
+
|
23
|
+
def initialize(id, uri, path = self.class.file_path)
|
24
|
+
super(id, path)
|
25
|
+
@uri = uri
|
26
|
+
end
|
27
|
+
|
28
|
+
def id
|
29
|
+
self.name
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(overwrite_existing_key = true)
|
33
|
+
super(overwrite_existing_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def add_self_to_yaml(yaml)
|
39
|
+
key = id.dup.force_encoding('ASCII')
|
40
|
+
yaml[key] = {'uri' => self.uri.force_encoding('ASCII')}
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/hue.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
APP_NAME = 'ruby_hue'
|
2
|
-
BASE = 'http://10.10.10.20/api'
|
3
|
-
# UUID = 'd79713a3433df3d972ba7c22cb1cc23e'
|
4
|
-
# Digest::MD5.hexdigest('aa4f6bc0-2045-0130-8cf0-0018de9ecdd0')
|
5
|
-
|
6
1
|
require 'net/http'
|
7
2
|
require 'json'
|
8
3
|
require 'matrix'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'uuid'
|
9
6
|
|
10
7
|
RGB_MATRIX = Matrix[
|
11
8
|
[3.233358361244897, -1.5262682428425947, 0.27916711262124544],
|
@@ -13,20 +10,98 @@ RGB_MATRIX = Matrix[
|
|
13
10
|
[0.12942207487871885, 0.19839858329512317, 2.0280912276039635]
|
14
11
|
]
|
15
12
|
|
16
|
-
require 'hue/bridge.rb'
|
17
|
-
require 'hue/bulb.rb'
|
18
|
-
require 'hue/config.rb'
|
19
|
-
|
20
13
|
module Hue
|
21
14
|
|
22
|
-
DEVICE_TYPE =
|
15
|
+
DEVICE_TYPE = 'hue-lib'
|
16
|
+
DEFAULT_UDP_TIMEOUT = 2
|
17
|
+
ERROR_DEFAULT_EXISTS = 'Default application already registered.'
|
18
|
+
ERROR_NO_BRIDGE_FOUND = 'No bridge found.'
|
23
19
|
|
24
20
|
def self.device_type
|
25
21
|
DEVICE_TYPE
|
26
22
|
end
|
27
23
|
|
28
|
-
def self.
|
29
|
-
|
24
|
+
def self.one_time_uuid
|
25
|
+
Digest::MD5.hexdigest(UUID.generate)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.register_default
|
29
|
+
if default = (Hue::Config::Application.default rescue nil)
|
30
|
+
raise Hue::Error.new(ERROR_DEFAULT_EXISTS)
|
31
|
+
else
|
32
|
+
bridge_config = register_bridges.values.first # Assuming one bridge for now
|
33
|
+
secret = Hue.one_time_uuid
|
34
|
+
app_config = Hue::Config::Application.new(bridge_config.id, secret)
|
35
|
+
puts "Registering app...(#{secret})"
|
36
|
+
instance = Hue::Bridge.new(app_config.id, bridge_config.uri)
|
37
|
+
instance.register
|
38
|
+
app_config.write
|
39
|
+
instance
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.application
|
44
|
+
application_config = Hue::Config::Application.default
|
45
|
+
bridge_config = Hue::Config::Bridge.find(application_config.bridge_id)
|
46
|
+
bridge_config ||= register_bridges[application_config.bridge_id]
|
47
|
+
|
48
|
+
if bridge_config.nil?
|
49
|
+
raise Error.new("Unable to find bridge: #{application_config.bridge_id}")
|
50
|
+
end
|
51
|
+
|
52
|
+
Hue::Bridge.new(application_config.id, bridge_config.uri)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.remove_default
|
56
|
+
instance = application
|
57
|
+
instance.unregister
|
58
|
+
Hue::Config::Application.default.delete
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.discover(timeout = DEFAULT_UDP_TIMEOUT)
|
63
|
+
bridges = Hash.new
|
64
|
+
payload = <<-PAYLOAD
|
65
|
+
M-SEARCH * HTTP/1.1
|
66
|
+
HOST: 239.255.255.250:1900
|
67
|
+
MAN: ssdp:discover
|
68
|
+
MX: 10
|
69
|
+
ST: ssdp:all
|
70
|
+
PAYLOAD
|
71
|
+
broadcast_address = '239.255.255.250'
|
72
|
+
port_number = 1900
|
73
|
+
|
74
|
+
socket = UDPSocket.new(Socket::AF_INET)
|
75
|
+
socket.send(payload, 0, broadcast_address, port_number)
|
76
|
+
|
77
|
+
Timeout.timeout(timeout, Hue::Error) do
|
78
|
+
loop do
|
79
|
+
message, (address_family, port, hostname, ip_add) = socket.recvfrom(1024)
|
80
|
+
if message =~ /IpBridge/ && location = /LOCATION: (.*)$/.match(message)
|
81
|
+
if uuid = /uuid:(.{36})/.match(message)
|
82
|
+
# Assume this is Philips Hue for now.
|
83
|
+
bridges[uuid.captures.first] = "http://#{ip_add}/api"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
rescue Hue::Error
|
90
|
+
bridges
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.register_bridges
|
94
|
+
bridges = self.discover
|
95
|
+
if bridges.empty?
|
96
|
+
raise Error.new(ERROR_NO_BRIDGE_FOUND)
|
97
|
+
else
|
98
|
+
bridges.inject(Hash.new) do |hash, (id, ip)|
|
99
|
+
config = Hue::Config::Bridge.new(id, ip)
|
100
|
+
config.write
|
101
|
+
hash[id] = config
|
102
|
+
hash
|
103
|
+
end
|
104
|
+
end
|
30
105
|
end
|
31
106
|
|
32
107
|
class Error < StandardError
|
@@ -57,3 +132,9 @@ module Hue
|
|
57
132
|
end
|
58
133
|
|
59
134
|
end
|
135
|
+
|
136
|
+
require 'hue/config/abstract'
|
137
|
+
require 'hue/config/application'
|
138
|
+
require 'hue/config/bridge'
|
139
|
+
require 'hue/bridge'
|
140
|
+
require 'hue/bulb'
|
data/spec/config/bridges.yml
CHANGED
data/spec/hue/bridge_spec.rb
CHANGED
@@ -2,36 +2,12 @@ require 'spec_helper.rb'
|
|
2
2
|
|
3
3
|
describe Hue::Bridge do
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
def klass
|
10
|
-
self.class.klass
|
11
|
-
end
|
12
|
-
|
13
|
-
# it 'should acts as a singleton and give access to the instance' do
|
14
|
-
# klass.instance.should be_a_kind_of(Hue::Bridge)
|
15
|
-
# end
|
16
|
-
|
17
|
-
it 'should allow registering a new bridge' do
|
18
|
-
pending
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should allow un-registering a bridge' do
|
22
|
-
pending
|
23
|
-
end
|
5
|
+
context 'when instantiated' do
|
6
|
+
bridge = test_bridge
|
24
7
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# before(:each) do
|
29
|
-
# with_fake_index_request
|
30
|
-
# end
|
31
|
-
|
32
|
-
it 'should report the bridge status' do
|
33
|
-
with_fake_request_base
|
34
|
-
bridge.status.should == api_reply(:base)
|
8
|
+
it 'should report the status' do
|
9
|
+
with_fake_request
|
10
|
+
bridge.status.should == api_reply(:get_success)
|
35
11
|
end
|
36
12
|
|
37
13
|
it 'should report errors' do
|
@@ -41,7 +17,7 @@ describe Hue::Bridge do
|
|
41
17
|
end.should raise_error(Hue::API::Error, 'unauthorized user')
|
42
18
|
end
|
43
19
|
|
44
|
-
it 'should report the
|
20
|
+
it 'should report the available lights' do
|
45
21
|
with_fake_request(:lights)
|
46
22
|
bridge.lights.should == api_reply(:lights)
|
47
23
|
end
|
@@ -70,6 +46,20 @@ describe Hue::Bridge do
|
|
70
46
|
end
|
71
47
|
end
|
72
48
|
|
49
|
+
it 'should allow unregistering an existing config' do
|
50
|
+
with_fake_delete("config/whitelist/#{TEST_APPLICATION_UUID}")
|
51
|
+
bridge.unregister
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when instantiated with a new config' do
|
56
|
+
new_id = 'new_test_id'
|
57
|
+
bridge = described_class.new(new_id, TEST_BRIDGE_URI)
|
58
|
+
|
59
|
+
it 'should allow registering the new config' do
|
60
|
+
with_fake_post(nil, {:username => new_id, :devicetype => Hue.device_type})
|
61
|
+
bridge.register
|
62
|
+
end
|
73
63
|
end
|
74
64
|
|
75
65
|
end
|
data/spec/hue/bulb_spec.rb
CHANGED
@@ -2,16 +2,8 @@ require 'spec_helper.rb'
|
|
2
2
|
|
3
3
|
describe Hue::Bulb do
|
4
4
|
|
5
|
-
def self.klass
|
6
|
-
Hue::Bulb
|
7
|
-
end
|
8
|
-
|
9
|
-
def klass
|
10
|
-
self.class.klass
|
11
|
-
end
|
12
|
-
|
13
5
|
context 'when instantiated with a given bridge and id' do
|
14
|
-
bulb =
|
6
|
+
bulb = described_class.new(test_bridge, 1)
|
15
7
|
|
16
8
|
before(:all) do
|
17
9
|
with_fake_request('lights/1')
|
@@ -104,4 +96,5 @@ describe Hue::Bulb do
|
|
104
96
|
end
|
105
97
|
|
106
98
|
end
|
99
|
+
|
107
100
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
class Hue::Config::AbstractTest < Hue::Config::Abstract
|
4
|
+
private
|
5
|
+
def add_self_to_yaml(yaml)
|
6
|
+
yaml[name] = { 1 => :test}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Hue::Config::Abstract do
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
create_test_application_config
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'given an new config' do
|
17
|
+
config = described_class.new('test', TEST_CONFIG_APPLICATION_PATH)
|
18
|
+
|
19
|
+
it 'should report the values' do
|
20
|
+
config.name == 'test'
|
21
|
+
config.path == TEST_CONFIG_APPLICATION_PATH
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should allow writing the new config to file' do
|
25
|
+
config.write
|
26
|
+
YAML.load_file(config.path)['test'].should be_a(Hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should allow deleting that named config from the file' do
|
30
|
+
config.delete
|
31
|
+
YAML.load_file(config.path)['test'].should be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hue::Config::Application do
|
4
|
+
|
5
|
+
mock_application_config_path
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
create_test_application_config
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should report the config file location' do
|
12
|
+
described_class.file_path.should == TEST_CONFIG_APPLICATION_PATH
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should throw and error if a named config doesn't exist" do
|
16
|
+
lambda do
|
17
|
+
described_class.named('not_default')
|
18
|
+
end.should raise_error(Hue::Config::NotFound, /Config named (.*) not found/)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with a config file, containing a default' do
|
22
|
+
config = described_class.default
|
23
|
+
|
24
|
+
it "should give the default config and report it's values" do
|
25
|
+
config.name == described_class::STRING_DEFAULT
|
26
|
+
config.bridge_id == TEST_CONFIG_APPLICATION[config.name][described_class::STRING_BRIDGE_ID]
|
27
|
+
config.id == TEST_CONFIG_APPLICATION[config.name][described_class::STRING_ID]
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should allow deleting the default config from the file' do
|
31
|
+
config.delete
|
32
|
+
YAML.load_file(described_class.file_path)[described_class::STRING_DEFAULT].should be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'given an new config' do
|
37
|
+
config = described_class.new('http://someip/api', 'some_id', 'not_default')
|
38
|
+
|
39
|
+
it 'should report the values' do
|
40
|
+
config.name == 'not_default'
|
41
|
+
config.bridge_id == 'http://someip/api'
|
42
|
+
config.id == 'not_default'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should allow writing the new config to file' do
|
46
|
+
config.write
|
47
|
+
YAML.load_file(described_class.file_path)['not_default'].should be_a(Hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should allow fetching that name config' do
|
51
|
+
named_config = described_class.named('not_default')
|
52
|
+
named_config.should == config
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should allow deleting that named config from the file' do
|
56
|
+
config.delete
|
57
|
+
YAML.load_file(described_class.file_path)['not_default'].should be_nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe Hue::Config::Bridge do
|
4
|
+
|
5
|
+
mock_bridge_config_path
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
create_test_bridge_config
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should report the config file location' do
|
12
|
+
described_class.file_path.should == TEST_CONFIG_BRIDGE_PATH
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should find existing bridges given an id' do
|
16
|
+
found = described_class.find(TEST_BRIDGE_UUID)
|
17
|
+
found.id.should == TEST_BRIDGE_UUID
|
18
|
+
found.uri.should == TEST_BRIDGE_URI
|
19
|
+
|
20
|
+
described_class.find('something').should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'given an new config' do
|
24
|
+
uuid = UUID.generate
|
25
|
+
uri = 'http://someip/api'
|
26
|
+
config = described_class.new(uuid, uri)
|
27
|
+
|
28
|
+
it 'should report the values' do
|
29
|
+
config.name == uuid
|
30
|
+
config.id == uuid
|
31
|
+
config.uri == uri
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/spec/hue_spec.rb
CHANGED
@@ -2,12 +2,79 @@ require 'spec_helper.rb'
|
|
2
2
|
|
3
3
|
describe Hue do
|
4
4
|
|
5
|
+
mock_application_config_path
|
6
|
+
mock_bridge_config_path
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
create_test_bridge_config
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
mock_udp_replies
|
14
|
+
end
|
15
|
+
|
5
16
|
it 'should report the device type as itself' do
|
6
|
-
|
17
|
+
described_class.device_type.should == described_class::DEVICE_TYPE
|
7
18
|
end
|
8
19
|
|
9
|
-
it 'should return the default
|
10
|
-
|
20
|
+
it 'should return the default application' do
|
21
|
+
described_class.application.should be_a(described_class::Bridge)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when discovering new bridges' do
|
25
|
+
it 'should return a list discovered bridges' do
|
26
|
+
bridges = Hue.discover
|
27
|
+
bridges.should == {TEST_UDP_BRIDGE_UUID => TEST_UDP_BRIDGE_URI}
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should allow registering of discovered bridges' do
|
31
|
+
Hue::Config::Bridge.find(TEST_UDP_BRIDGE_UUID).should be_nil
|
32
|
+
registered = Hue.register_bridges
|
33
|
+
new_bridge = registered[TEST_UDP_BRIDGE_UUID]
|
34
|
+
new_bridge.id.should == TEST_UDP_BRIDGE_UUID
|
35
|
+
new_bridge.uri.should == TEST_UDP_BRIDGE_URI
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'after discovering bridges' do
|
40
|
+
before(:each) do
|
41
|
+
mock_udp_replies(TEST_BRIDGE_UUID, 'new_host')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should update already registered bridges' do
|
45
|
+
bridge = Hue::Config::Bridge.find(TEST_BRIDGE_UUID)
|
46
|
+
bridge.should_not be_nil
|
47
|
+
|
48
|
+
registered = Hue.register_bridges
|
49
|
+
|
50
|
+
updated_bridge = registered[TEST_BRIDGE_UUID]
|
51
|
+
updated_bridge.id.should == TEST_BRIDGE_UUID
|
52
|
+
updated_bridge.uri.should_not == bridge.uri
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when registering or un-registering the application' do
|
57
|
+
it 'should throw and error if the default already exists' do
|
58
|
+
lambda do
|
59
|
+
described_class.register_default
|
60
|
+
end.should raise_error(described_class::Error, described_class::ERROR_DEFAULT_EXISTS)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should allow a new default if one doesn\'t exist' do
|
64
|
+
with_temp_config_path do
|
65
|
+
with_fake_post(nil, {}, 'post_success', TEST_UDP_BRIDGE_URI)
|
66
|
+
with_stdout(/Registering app...(.*)$/) do
|
67
|
+
instance = described_class.register_default
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should allow un-registering the default' do
|
73
|
+
with_temp_config_path(true) do
|
74
|
+
with_fake_delete("config/whitelist/#{TEST_APPLICATION_UUID}")
|
75
|
+
instance = described_class.remove_default
|
76
|
+
end
|
77
|
+
end
|
11
78
|
end
|
12
79
|
|
13
80
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
[{}]
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
{"error"=>{"type"=>101, "address"=>"", "description"=>"link button not pressed"}}
|
@@ -0,0 +1 @@
|
|
1
|
+
[{}]
|
data/spec/spec_helper.rb
CHANGED
@@ -5,21 +5,113 @@ require 'mocha'
|
|
5
5
|
|
6
6
|
WebMock.disable_net_connect!
|
7
7
|
|
8
|
+
# GENERAL
|
9
|
+
|
10
|
+
def join_paths(*paths)
|
11
|
+
File.join(paths.delete_if { |entry| entry.nil? || entry.empty? })
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_stdout(expected_output, &block)
|
15
|
+
original_stdout = $stdout
|
16
|
+
new_stdout = StringIO.new
|
17
|
+
begin
|
18
|
+
$stdout = new_stdout
|
19
|
+
yield
|
20
|
+
new_stdout.seek(0)
|
21
|
+
output = new_stdout.read
|
22
|
+
output.should match(expected_output)
|
23
|
+
ensure
|
24
|
+
new_stdout.close
|
25
|
+
$stdout = original_stdout
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def silence_warnings
|
30
|
+
begin
|
31
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
$VERBOSE = old_verbose
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
8
38
|
SPEC_DIR = File.dirname(__FILE__)
|
9
|
-
|
10
|
-
|
39
|
+
|
40
|
+
silence_warnings do
|
41
|
+
Hue.const_set(:DEFAULT_UDP_TIMEOUT, 0.01)
|
42
|
+
end
|
43
|
+
|
44
|
+
# APPLICATION CONFIG
|
45
|
+
|
46
|
+
TEST_CONFIG_APPLICATION_PATH = File.join(SPEC_DIR, 'config', 'applications.yml')
|
47
|
+
TEST_CONFIG_APPLICATION = YAML.load_file(TEST_CONFIG_APPLICATION_PATH)
|
11
48
|
TEST_JSON_DATA_PATH = File.join(SPEC_DIR, 'json')
|
12
49
|
|
13
|
-
|
50
|
+
def create_test_application_config(path = TEST_CONFIG_APPLICATION_PATH)
|
51
|
+
File.open(path, 'w' ) do |out|
|
52
|
+
YAML.dump(TEST_CONFIG_APPLICATION, out)
|
53
|
+
end
|
54
|
+
end
|
14
55
|
|
15
|
-
def
|
16
|
-
|
17
|
-
to_return(:status => 200, :body => api_reply_json(:base), :headers => {})
|
56
|
+
def mock_application_config_path
|
57
|
+
Hue::Config::Application.stubs(:file_path).returns(TEST_CONFIG_APPLICATION_PATH)
|
18
58
|
end
|
19
59
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
60
|
+
def with_temp_config_path(write_config = false)
|
61
|
+
temp_config_path = File.join(SPEC_DIR, 'config', 'temp')
|
62
|
+
FileUtils.mkdir_p(temp_config_path)
|
63
|
+
temp_config = File.join(temp_config_path, 'applications.yml')
|
64
|
+
if write_config
|
65
|
+
create_test_application_config(temp_config)
|
66
|
+
end
|
67
|
+
Hue::Config::Application.expects(:file_path).at_least_once.returns(temp_config)
|
68
|
+
|
69
|
+
begin
|
70
|
+
yield
|
71
|
+
ensure
|
72
|
+
FileUtils.rm_f(temp_config)
|
73
|
+
mock_bridge_config_path
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# BRIDGE CONFIG
|
78
|
+
|
79
|
+
TEST_CONFIG_BRIDGE_PATH = File.join(SPEC_DIR, 'config', 'bridges.yml')
|
80
|
+
TEST_BRIDGE_UUID = 'bc6be180-4c57-0130-8d8f-0018de9ecdd0'
|
81
|
+
TEST_CONFIG_BRIDGE = YAML.load_file(TEST_CONFIG_BRIDGE_PATH)
|
82
|
+
|
83
|
+
def create_test_bridge_config
|
84
|
+
File.open(TEST_CONFIG_BRIDGE_PATH, 'w' ) do |out|
|
85
|
+
YAML.dump(TEST_CONFIG_BRIDGE, out)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def mock_bridge_config_path
|
90
|
+
Hue::Config::Bridge.stubs(:file_path).returns(TEST_CONFIG_BRIDGE_PATH)
|
91
|
+
end
|
92
|
+
|
93
|
+
# HUE - UDP
|
94
|
+
|
95
|
+
TEST_UDP_BRIDGE_UUID = '09230030-4c1e-0130-8d83-0018de9ecdd0'
|
96
|
+
TEST_UDP_BRIDGE_HOSTNAME = 'upd-host'
|
97
|
+
TEST_UDP_BRIDGE_URI = "http://#{TEST_UDP_BRIDGE_HOSTNAME}/api"
|
98
|
+
|
99
|
+
def mock_udp_replies(uuid = TEST_UDP_BRIDGE_UUID, hostname = TEST_UDP_BRIDGE_HOSTNAME)
|
100
|
+
reply = ["HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=100\r\nEXT:\r\nLOCATION: http://127.0.0.1:80/description.xml\r\nSERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/0.1\r\nST: upnp:rootdevice\r\nUSN: uuid:#{uuid}::upnp:rootdevice\r\n\r\n", ["AF_INET", 1900, "127.0.0.1", hostname]]
|
101
|
+
|
102
|
+
socket = Object.new
|
103
|
+
socket.stubs(:send).returns(nil)
|
104
|
+
socket.stubs(:recvfrom).returns(reply)
|
105
|
+
UDPSocket.stubs(:new).returns(socket)
|
106
|
+
end
|
107
|
+
|
108
|
+
# BRIDGE - API CALLS
|
109
|
+
|
110
|
+
TEST_BRIDGE_URI = 'http://localhost/api'
|
111
|
+
TEST_APPLICATION_UUID = 'application_uuid'
|
112
|
+
|
113
|
+
def test_bridge
|
114
|
+
Hue::Bridge.new(TEST_APPLICATION_UUID, TEST_BRIDGE_URI)
|
23
115
|
end
|
24
116
|
|
25
117
|
def api_reply_json(named)
|
@@ -32,10 +124,37 @@ def api_reply(named)
|
|
32
124
|
JSON.parse(api_reply_json(named))
|
33
125
|
end
|
34
126
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
to_return(:status => 200, :body =>
|
127
|
+
def with_fake_request(named = nil, body_name = nil)
|
128
|
+
body_name ||= (named.nil? ? 'get_success' : named)
|
129
|
+
stub_request(:get, join_paths(TEST_BRIDGE_URI, TEST_APPLICATION_UUID, named.to_s)).
|
130
|
+
to_return(:status => 200, :body => api_reply_json(body_name), :headers => {})
|
131
|
+
end
|
132
|
+
|
133
|
+
def with_fake_update(named, put_body = {})
|
134
|
+
stub = stub_request(:put, "#{TEST_BRIDGE_URI}/#{TEST_APPLICATION_UUID}/#{named.to_s}").
|
135
|
+
with(:body => put_body.to_json).
|
136
|
+
to_return(:status => 200, :body => api_reply_json(:put_success), :headers => {})
|
137
|
+
|
138
|
+
if block_given?
|
139
|
+
yield
|
140
|
+
stub.should have_been_requested
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def with_fake_post(named, post_body = {}, post_reply_name = 'post_success', uri = TEST_BRIDGE_URI)
|
145
|
+
stub = stub_request(:post, join_paths(uri, named))
|
146
|
+
stub.with(:body => post_body.to_json) unless post_body.empty?
|
147
|
+
stub.to_return(:status => 200, :body => api_reply_json(join_paths(named, post_reply_name)), :headers => {})
|
148
|
+
|
149
|
+
if block_given?
|
150
|
+
yield
|
151
|
+
stub.should have_been_requested
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def with_fake_delete(named, delete_reply = 'delete_success')
|
156
|
+
stub = stub_request(:delete, "#{TEST_BRIDGE_URI}/#{TEST_APPLICATION_UUID}/#{named.to_s}").
|
157
|
+
to_return(:status => 200, :body => api_reply_json(join_paths(named, delete_reply)), :headers => {})
|
39
158
|
|
40
159
|
if block_given?
|
41
160
|
yield
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hue-lib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Birkir A. Barkarson
|
9
|
-
-
|
9
|
+
- Aaron Hurley
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-01-
|
13
|
+
date: 2013-01-30 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: json
|
@@ -83,6 +83,7 @@ executables: []
|
|
83
83
|
extensions: []
|
84
84
|
extra_rdoc_files: []
|
85
85
|
files:
|
86
|
+
- .gitignore
|
86
87
|
- Gemfile
|
87
88
|
- README.md
|
88
89
|
- Rakefile
|
@@ -90,16 +91,25 @@ files:
|
|
90
91
|
- lib/hue.rb
|
91
92
|
- lib/hue/bridge.rb
|
92
93
|
- lib/hue/bulb.rb
|
93
|
-
- lib/hue/config.rb
|
94
|
+
- lib/hue/config/abstract.rb
|
95
|
+
- lib/hue/config/application.rb
|
96
|
+
- lib/hue/config/bridge.rb
|
97
|
+
- spec/config/applications.yml
|
94
98
|
- spec/config/bridges.yml
|
95
99
|
- spec/hue/bridge_spec.rb
|
96
100
|
- spec/hue/bulb_spec.rb
|
97
|
-
- spec/hue/
|
101
|
+
- spec/hue/config/abstract_spec.rb
|
102
|
+
- spec/hue/config/application_spec.rb
|
103
|
+
- spec/hue/config/bridge_spec.rb
|
98
104
|
- spec/hue_spec.rb
|
99
|
-
- spec/json/base.json
|
100
105
|
- spec/json/config.json
|
106
|
+
- spec/json/config/whitelist/application_uuid/delete_success.json
|
107
|
+
- spec/json/get_success.json
|
101
108
|
- spec/json/lights.json
|
102
109
|
- spec/json/lights/1.json
|
110
|
+
- spec/json/post_error.json
|
111
|
+
- spec/json/post_success.json
|
112
|
+
- spec/json/put_success.json
|
103
113
|
- spec/json/schedules.json
|
104
114
|
- spec/json/unauthorized.json
|
105
115
|
- spec/spec_helper.rb
|
data/lib/hue/config.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
module Hue
|
2
|
-
class Config
|
3
|
-
STRING_DEFAULT = 'default'
|
4
|
-
STRING_BASE_URI = 'base_uri'
|
5
|
-
STRING_IDENTIFIER = 'identifier'
|
6
|
-
|
7
|
-
require 'yaml'
|
8
|
-
require 'fileutils'
|
9
|
-
|
10
|
-
def self.bridges_config_path
|
11
|
-
File.join(ENV['HOME'], ".#{APP_NAME}", 'bridges.yml')
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.default
|
15
|
-
named(STRING_DEFAULT)
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.named(name)
|
19
|
-
yaml = read_file
|
20
|
-
if named_yaml = yaml[name]
|
21
|
-
Config.new(named_yaml[STRING_BASE_URI], named_yaml[STRING_IDENTIFIER], name)
|
22
|
-
else
|
23
|
-
raise Error.new("Config named '#{name}' not found.")
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
public
|
28
|
-
|
29
|
-
attr_reader :base_uri, :identifier, :name
|
30
|
-
|
31
|
-
def initialize(base_uri, identifier, name = STRING_DEFAULT)
|
32
|
-
@base_uri = base_uri
|
33
|
-
@identifier = identifier
|
34
|
-
@name = name
|
35
|
-
end
|
36
|
-
|
37
|
-
def write(config_file = self.class.bridges_config_path)
|
38
|
-
yaml = YAML.load_file(self.class.bridges_config_path) rescue Hash::New
|
39
|
-
if yaml.key?(name)
|
40
|
-
raise "Configuration named '#{name}' already exists in #{config_file}\nPlease de-register before creating a new one with the same name."
|
41
|
-
else
|
42
|
-
yaml[name] = {
|
43
|
-
STRING_BASE_URI => self.base_uri,
|
44
|
-
STRING_IDENTIFIER => identifier.force_encoding('ASCII') # Avoid binary encoded YAML
|
45
|
-
}
|
46
|
-
self.class.setup_config_path(config_file)
|
47
|
-
File.open(config_file, 'w+' ) do |out|
|
48
|
-
YAML.dump(yaml, out)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def delete
|
54
|
-
config_file = self.class.bridges_config_path
|
55
|
-
yaml = YAML.load_file(config_file) rescue Hash::New
|
56
|
-
|
57
|
-
if yaml.key?(name)
|
58
|
-
yaml.delete(name)
|
59
|
-
end
|
60
|
-
|
61
|
-
if yaml.size > 0
|
62
|
-
self.class.setup_config_path(config_file)
|
63
|
-
File.open(config_file, 'w+' ) do |out|
|
64
|
-
YAML.dump(yaml, out)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def ==(rhs)
|
70
|
-
lhs = self
|
71
|
-
|
72
|
-
lhs.class == rhs.class &&
|
73
|
-
lhs.name == rhs.name &&
|
74
|
-
lhs.base_uri == rhs.base_uri &&
|
75
|
-
lhs.identifier == rhs.identifier
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def self.setup_config_path(path)
|
81
|
-
dir = File.dirname(path)
|
82
|
-
FileUtils.mkdir_p(dir) unless Dir.exists?(dir)
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.read_file(config_file = bridges_config_path)
|
86
|
-
begin
|
87
|
-
yaml = YAML.load_file(config_file)
|
88
|
-
rescue => err
|
89
|
-
raise Error.new("Failed to read configuration file", err)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
data/spec/hue/config_spec.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'spec_helper.rb'
|
2
|
-
|
3
|
-
describe Hue::Config do
|
4
|
-
|
5
|
-
TEST_IDENTIFIER = 'test_identifier'
|
6
|
-
|
7
|
-
def self.klass
|
8
|
-
Hue::Config
|
9
|
-
end
|
10
|
-
|
11
|
-
def klass
|
12
|
-
self.class.klass
|
13
|
-
end
|
14
|
-
|
15
|
-
after(:all) do
|
16
|
-
File.open(TEST_BRIDGE_CONFIG_PATH, 'w' ) do |out|
|
17
|
-
YAML.dump(TEST_BRIDGE_CONFIG, out)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should report the bridge config file location' do
|
22
|
-
klass.bridges_config_path.should == TEST_BRIDGE_CONFIG_PATH
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should throw and error if a named config doesn't exist" do
|
26
|
-
lambda do
|
27
|
-
klass.named('not_default')
|
28
|
-
end.should raise_error(Hue::Error, /Config named (.*) not found/)
|
29
|
-
end
|
30
|
-
|
31
|
-
context 'with a bridge config file, containing the default bridge' do
|
32
|
-
it "should give the default config and report it's values" do
|
33
|
-
config = klass.default
|
34
|
-
config.name == klass::STRING_DEFAULT
|
35
|
-
config.base_uri == TEST_BRIDGE_CONFIG[config.name][klass::STRING_BASE_URI]
|
36
|
-
config.identifier == TEST_BRIDGE_CONFIG[config.name][klass::STRING_IDENTIFIER]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context 'given an new config' do
|
41
|
-
config = klass.new('http://someip/api', 'some_id', 'not_default')
|
42
|
-
|
43
|
-
it 'should report the values' do
|
44
|
-
config.name == 'not_default'
|
45
|
-
config.base_uri == 'http://someip/api'
|
46
|
-
config.identifier == 'not_default'
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'should allow writing the new config to file' do
|
50
|
-
config.write
|
51
|
-
YAML.load_file(klass.bridges_config_path)['not_default'].should be_a(Hash)
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'should allow fetching that name config' do
|
55
|
-
named_config = klass.named('not_default')
|
56
|
-
named_config.should == config
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'should allow deleting that named config from the file' do
|
60
|
-
config.delete
|
61
|
-
YAML.load_file(klass.bridges_config_path)['not_default'].should be_nil
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|