huebot 0.4.0 → 1.0.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/lib/hue/client.rb DELETED
@@ -1,141 +0,0 @@
1
- require 'net/http'
2
- require 'json'
3
-
4
- module Hue
5
- class Client
6
- attr_reader :username
7
-
8
- def initialize(username = nil)
9
- @bridge_id = nil
10
- @username = username || find_username
11
-
12
- if @username
13
- begin
14
- validate_user
15
- rescue Hue::UnauthorizedUser
16
- register_user
17
- end
18
- else
19
- register_user
20
- end
21
- end
22
-
23
- def bridge
24
- @bridge_id = find_bridge_id unless @bridge_id
25
- if @bridge_id
26
- bridge = bridges.select { |b| b.id == @bridge_id }.first
27
- else
28
- bridge = bridges.first
29
- end
30
- raise NoBridgeFound unless bridge
31
- bridge
32
- end
33
-
34
- # Patched by Jordan Hollinger to remove use of "curb" gem
35
- # TODO handle redirects? That's what curb was used for.
36
- def bridges
37
- req = Net::HTTP::Get.new(URI(ENV["HUE_DISCOVERY_API"] || "https://discovery.meethue.com/"))
38
- resp = Net::HTTP.start req.uri.host, req.uri.port, {use_ssl: true} do |http|
39
- http.request req
40
- end
41
-
42
- JSON(resp.body).lazy.map { |x|
43
- Bridge.new(self, x)
44
- }
45
-
46
- rescue Net::OpenTimeout, Net::ReadTimeout, JSON::ParserError
47
- []
48
- end
49
-
50
- def lights
51
- bridge.lights
52
- end
53
-
54
- def add_lights
55
- bridge.add_lights
56
- end
57
-
58
- def light(id)
59
- id = id.to_s
60
- lights.select { |l| l.id == id }.first
61
- end
62
-
63
- def groups
64
- bridge.groups
65
- end
66
-
67
- def group(id = nil)
68
- return Group.new(self, bridge) if id.nil?
69
-
70
- id = id.to_s
71
- groups.select { |g| g.id == id }.first
72
- end
73
-
74
- def scenes
75
- bridge.scenes
76
- end
77
-
78
- def scene(id)
79
- id = id.to_s
80
- scenes.select { |s| s.id == id }.first
81
- end
82
-
83
- private
84
-
85
- def find_username
86
- return ENV['HUE_USERNAME'] if ENV['HUE_USERNAME']
87
-
88
- json = JSON(File.read(File.expand_path('~/.hue')))
89
- json['username']
90
- rescue
91
- return nil
92
- end
93
-
94
- def validate_user
95
- response = JSON(Net::HTTP.get(URI.parse("http://#{bridge.ip}/api/#{@username}")))
96
-
97
- if response.is_a? Array
98
- response = response.first
99
- end
100
-
101
- if error = response['error']
102
- raise get_error(error)
103
- end
104
-
105
- response['success']
106
- end
107
-
108
- def register_user
109
- body = JSON.dump({
110
- devicetype: 'Ruby'
111
- })
112
-
113
- uri = URI.parse("http://#{bridge.ip}/api")
114
- http = Net::HTTP.new(uri.host)
115
- response = JSON(http.request_post(uri.path, body).body).first
116
-
117
- if error = response['error']
118
- raise get_error(error)
119
- end
120
-
121
- if @username = response['success']['username']
122
- File.write(File.expand_path('~/.hue'), JSON.dump({username: @username}))
123
- end
124
- end
125
-
126
- def find_bridge_id
127
- return ENV['HUE_BRIDGE_ID'] if ENV['HUE_BRIDGE_ID']
128
-
129
- json = JSON(File.read(File.expand_path('~/.hue')))
130
- json['bridge_id']
131
- rescue
132
- return nil
133
- end
134
-
135
- def get_error(error)
136
- # Find error class and return instance
137
- klass = Hue::ERROR_MAP[error['type']] || UnknownError unless klass
138
- klass.new(error['description'])
139
- end
140
- end
141
- end
@@ -1,27 +0,0 @@
1
- module Hue
2
- module EditableState
3
- def on?
4
- @state['on']
5
- end
6
-
7
- def on!
8
- self.on = true
9
- end
10
-
11
- def off!
12
- self.on = false
13
- end
14
-
15
- %w{on hue saturation brightness color_temperature alert effect}.each do |key|
16
- define_method "#{key}=".to_sym do |value|
17
- set_state({key.to_sym => value})
18
- instance_variable_set("@#{key}".to_sym, value)
19
- end
20
- end
21
-
22
- def set_xy(x, y)
23
- set_state({:xy => [x, y]})
24
- @x, @y = x, y
25
- end
26
- end
27
- end
data/lib/hue/errors.rb DELETED
@@ -1,38 +0,0 @@
1
- module Hue
2
- class Error < StandardError; end
3
-
4
- class UnauthorizedUser < Error; end
5
- class InvalidJSON < Error; end
6
- class ResourceNotAvailable < Error; end
7
- class MethodNotAvailable < Error; end
8
- class MissingBody < Error; end
9
- class ParameterNotAvailable < Error; end
10
- class InvalidValueForParameter < Error; end
11
- class ParameterNotModifiable < Error; end
12
- class InternalError < Error; end
13
- class LinkButtonNotPressed < Error; end
14
- class ParameterNotModifiableWhileOff < ParameterNotModifiable; end
15
- class TooManyGroups < Error; end
16
- class GroupTooFull < Error; end
17
-
18
- class InvalidUsername < Error; end
19
- class UnknownError < Error; end
20
- class NoBridgeFound < Error; end
21
-
22
- # Status code to exception map
23
- ERROR_MAP = {
24
- 1 => Hue::UnauthorizedUser,
25
- 2 => Hue::InvalidJSON,
26
- 3 => Hue::ResourceNotAvailable,
27
- 4 => Hue::MethodNotAvailable,
28
- 5 => Hue::MissingBody,
29
- 6 => Hue::ParameterNotAvailable,
30
- 7 => Hue::InvalidValueForParameter,
31
- 8 => Hue::ParameterNotModifiable,
32
- 901 => Hue::InternalError,
33
- 101 => Hue::LinkButtonNotPressed,
34
- 201 => Hue::ParameterNotModifiableWhileOff,
35
- 301 => Hue::TooManyGroups,
36
- 302 => Hue::GroupTooFull
37
- }
38
- end
data/lib/hue/group.rb DELETED
@@ -1,181 +0,0 @@
1
- module Hue
2
- class Group
3
- include Enumerable
4
- include TranslateKeys
5
- include EditableState
6
-
7
- # Unique identification number.
8
- attr_reader :id
9
-
10
- # Bridge the group is associated with
11
- attr_reader :bridge
12
-
13
- # A unique, editable name given to the group.
14
- attr_accessor :name
15
-
16
- # Hue of the group. This is a wrapping value between 0 and 65535.
17
- # Both 0 and 65535 are red, 25500 is green and 46920 is blue.
18
- attr_accessor :hue
19
-
20
- # Saturation of the group. 255 is the most saturated (colored)
21
- # and 0 is the least saturated (white).
22
- attr_accessor :saturation
23
-
24
- # Brightness of the group. This is a scale from the minimum
25
- # brightness the group is capable of, 0, to the maximum capable
26
- # brightness, 255. Note a brightness of 0 is not off.
27
- attr_accessor :brightness
28
-
29
- # The x coordinate of a color in CIE color space. Between 0 and 1.
30
- #
31
- # @see http://developers.meethue.com/coreconcepts.html#color_gets_more_complicated
32
- attr_reader :x
33
-
34
- # The y coordinate of a color in CIE color space. Between 0 and 1.
35
- #
36
- # @see http://developers.meethue.com/coreconcepts.html#color_gets_more_complicated
37
- attr_reader :y
38
-
39
- # The Mired Color temperature of the light. 2012 connected lights
40
- # are capable of 153 (6500K) to 500 (2000K).
41
- #
42
- # @see http://en.wikipedia.org/wiki/Mired
43
- attr_accessor :color_temperature
44
-
45
- # A fixed name describing the type of group.
46
- attr_reader :type
47
-
48
- def initialize(client, bridge, id = nil, data = {})
49
- @client = client
50
- @bridge = bridge
51
- @id = id
52
-
53
- unpack(data)
54
- end
55
-
56
- def each(&block)
57
- lights.each(&block)
58
- end
59
-
60
- def lights
61
- @lights ||= begin
62
- @light_ids.map do |light_id|
63
- @client.light(light_id)
64
- end
65
- end
66
- end
67
-
68
- def name=(name)
69
- resp = set_group_state({:name => name})
70
- @name = new? ? name : resp[0]['success']["/groups/#{id}/name"]
71
- end
72
-
73
- def lights=(light_ids)
74
- light_ids.map! do |light_id|
75
- light_id.is_a?(Light) ? light_id.id : light_id.to_s
76
- end
77
-
78
- @light_ids = light_ids.uniq
79
- @lights = nil # resets the memoization
80
-
81
- set_group_state({:lights => @light_ids})
82
- end
83
-
84
- def scene=(scene)
85
- scene_id = scene.is_a?(Scene) ? scene.id : scene
86
- set_group_state({:scene => scene_id})
87
- end
88
-
89
- def <<(light_id)
90
- @light_ids << light_id
91
- set_group_state({:lights => @light_ids})
92
- end
93
- alias_method :add_light, :<<
94
-
95
- def set_group_state(attributes)
96
- return if new?
97
- body = translate_keys(attributes, GROUP_KEYS_MAP)
98
-
99
- uri = URI.parse(base_url)
100
- http = Net::HTTP.new(uri.host)
101
- response = http.request_put(uri.path, JSON.dump(body))
102
- JSON(response.body)
103
- end
104
-
105
- def set_state(attributes)
106
- return if new?
107
- body = translate_keys(attributes, STATE_KEYS_MAP)
108
-
109
- uri = URI.parse("#{base_url}/action")
110
- http = Net::HTTP.new(uri.host)
111
- response = http.request_put(uri.path, JSON.dump(body))
112
- JSON(response.body)
113
- end
114
-
115
- def refresh
116
- json = JSON(Net::HTTP.get(URI.parse(base_url)))
117
- unpack(json)
118
- @lights = nil
119
- end
120
-
121
- def create!
122
- body = {
123
- :name => @name,
124
- :lights => @light_ids,
125
- }
126
-
127
- uri = URI.parse("http://#{@bridge.ip}/api/#{@client.username}/groups")
128
- http = Net::HTTP.new(uri.host)
129
- response = http.request_post(uri.path, JSON.dump(body))
130
- json = JSON(response.body)
131
-
132
- @id = json[0]['success']['id']
133
- end
134
-
135
- def destroy!
136
- uri = URI.parse(base_url)
137
- http = Net::HTTP.new(uri.host)
138
- response = http.delete(uri.path)
139
- json = JSON(response.body)
140
- @id = nil if json[0]['success']
141
- end
142
-
143
- def new?
144
- @id.nil?
145
- end
146
-
147
- private
148
-
149
- GROUP_KEYS_MAP = {
150
- :name => :name,
151
- :light_ids => :lights,
152
- :type => :type,
153
- :state => :action
154
- }
155
-
156
- STATE_KEYS_MAP = {
157
- :on => :on,
158
- :brightness => :bri,
159
- :hue => :hue,
160
- :saturation => :sat,
161
- :xy => :xy,
162
- :color_temperature => :ct,
163
- :alert => :alert,
164
- :effect => :effect,
165
- :color_mode => :colormode,
166
- }
167
-
168
- def unpack(data)
169
- unpack_hash(data, GROUP_KEYS_MAP)
170
-
171
- unless new?
172
- unpack_hash(@state, STATE_KEYS_MAP)
173
- @x, @y = @state['xy']
174
- end
175
- end
176
-
177
- def base_url
178
- "http://#{@bridge.ip}/api/#{@client.username}/groups/#{id}"
179
- end
180
- end
181
- end
data/lib/hue/light.rb DELETED
@@ -1,177 +0,0 @@
1
- module Hue
2
- class Light
3
- include TranslateKeys
4
- include EditableState
5
-
6
- HUE_RANGE = 0..65535
7
- SATURATION_RANGE = 0..255
8
- BRIGHTNESS_RANGE = 0..255
9
- COLOR_TEMPERATURE_RANGE = 153..500
10
-
11
- # Unique identification number.
12
- attr_reader :id
13
-
14
- # Bridge the light is associated with
15
- attr_reader :bridge
16
-
17
- # A unique, editable name given to the light.
18
- attr_accessor :name
19
-
20
- # Hue of the light. This is a wrapping value between 0 and 65535.
21
- # Both 0 and 65535 are red, 25500 is green and 46920 is blue.
22
- attr_reader :hue
23
-
24
- # Saturation of the light. 255 is the most saturated (colored)
25
- # and 0 is the least saturated (white).
26
- attr_reader :saturation
27
-
28
- # Brightness of the light. This is a scale from the minimum
29
- # brightness the light is capable of, 0, to the maximum capable
30
- # brightness, 255. Note a brightness of 0 is not off.
31
- attr_reader :brightness
32
-
33
- # The x coordinate of a color in CIE color space. Between 0 and 1.
34
- #
35
- # @see http://developers.meethue.com/coreconcepts.html#color_gets_more_complicated
36
- attr_reader :x
37
-
38
- # The y coordinate of a color in CIE color space. Between 0 and 1.
39
- #
40
- # @see http://developers.meethue.com/coreconcepts.html#color_gets_more_complicated
41
- attr_reader :y
42
-
43
- # The Mired Color temperature of the light. 2012 connected lights
44
- # are capable of 153 (6500K) to 500 (2000K).
45
- #
46
- # @see http://en.wikipedia.org/wiki/Mired
47
- attr_reader :color_temperature
48
-
49
- # The alert effect, which is a temporary change to the bulb’s state.
50
- # This can take one of the following values:
51
- # * `none` – The light is not performing an alert effect.
52
- # * `select` – The light is performing one breathe cycle.
53
- # * `lselect` – The light is performing breathe cycles for 30 seconds
54
- # or until an "alert": "none" command is received.
55
- #
56
- # Note that in version 1.0 this contains the last alert sent to the
57
- # light and not its current state. This will be changed to contain the
58
- # current state in an upcoming patch.
59
- #
60
- # @see http://developers.meethue.com/coreconcepts.html#some_extra_fun_stuff
61
- attr_reader :alert
62
-
63
- # The dynamic effect of the light, can either be `none` or
64
- # `colorloop`. If set to colorloop, the light will cycle through
65
- # all hues using the current brightness and saturation settings.
66
- attr_reader :effect
67
-
68
- # Indicates the color mode in which the light is working, this is
69
- # the last command type it received. Values are `hs` for Hue and
70
- # Saturation, `xy` for XY and `ct` for Color Temperature. This
71
- # parameter is only present when the light supports at least one
72
- # of the values.
73
- attr_reader :color_mode
74
-
75
- # A fixed name describing the type of light.
76
- attr_reader :type
77
-
78
- # The hardware model of the light.
79
- attr_reader :model
80
-
81
- # An identifier for the software version running on the light.
82
- attr_reader :software_version
83
-
84
- # Reserved for future functionality.
85
- attr_reader :point_symbol
86
-
87
- def initialize(client, bridge, id, hash)
88
- @client = client
89
- @bridge = bridge
90
- @id = id
91
- unpack(hash)
92
- end
93
-
94
- def name=(new_name)
95
- unless (1..32).include?(new_name.length)
96
- raise InvalidValueForParameter, 'name must be between 1 and 32 characters.'
97
- end
98
-
99
- body = {
100
- :name => new_name
101
- }
102
-
103
- uri = URI.parse(base_url)
104
- http = Net::HTTP.new(uri.host)
105
- response = http.request_put(uri.path, JSON.dump(body))
106
- response = JSON(response.body).first
107
- if response['success']
108
- @name = new_name
109
- # else
110
- # TODO: Error
111
- end
112
- end
113
-
114
- # Indicates if a light can be reached by the bridge. Currently
115
- # always returns true, functionality will be added in a future
116
- # patch.
117
- def reachable?
118
- @state['reachable']
119
- end
120
-
121
- # @param transition The duration of the transition from the light’s current
122
- # state to the new state. This is given as a multiple of 100ms and
123
- # defaults to 4 (400ms). For example, setting transistiontime:10 will
124
- # make the transition last 1 second.
125
- def set_state(attributes, transition = nil)
126
- body = translate_keys(attributes, STATE_KEYS_MAP)
127
-
128
- # Add transition
129
- body.merge!({:transitiontime => transition}) if transition
130
-
131
- uri = URI.parse("#{base_url}/state")
132
- http = Net::HTTP.new(uri.host)
133
- response = http.request_put(uri.path, JSON.dump(body))
134
- JSON(response.body)
135
- end
136
-
137
- # Refresh the state of the lamp
138
- def refresh
139
- json = JSON(Net::HTTP.get(URI.parse(base_url)))
140
- unpack(json)
141
- end
142
-
143
- private
144
-
145
- KEYS_MAP = {
146
- :state => :state,
147
- :type => :type,
148
- :name => :name,
149
- :model => :modelid,
150
- :software_version => :swversion,
151
- :point_symbol => :pointsymbol
152
- }
153
-
154
- STATE_KEYS_MAP = {
155
- :on => :on,
156
- :brightness => :bri,
157
- :hue => :hue,
158
- :saturation => :sat,
159
- :xy => :xy,
160
- :color_temperature => :ct,
161
- :alert => :alert,
162
- :effect => :effect,
163
- :color_mode => :colormode,
164
- :reachable => :reachable,
165
- }
166
-
167
- def unpack(hash)
168
- unpack_hash(hash, KEYS_MAP)
169
- unpack_hash(@state, STATE_KEYS_MAP)
170
- @x, @y = @state['xy']
171
- end
172
-
173
- def base_url
174
- "http://#{@bridge.ip}/api/#{@client.username}/lights/#{id}"
175
- end
176
- end
177
- end
data/lib/hue/scene.rb DELETED
@@ -1,50 +0,0 @@
1
- module Hue
2
- class Scene
3
- include Enumerable
4
- include TranslateKeys
5
-
6
- # Unique identification number.
7
- attr_reader :id
8
-
9
- # Bridge the scene is associated with
10
- attr_reader :bridge
11
-
12
- # A unique, editable name given to the scene.
13
- attr_accessor :name
14
-
15
- # Whether or not the scene is active on a group.
16
- attr_reader :active
17
-
18
- def initialize(client, bridge, id, data)
19
- @client = client
20
- @bridge = bridge
21
- @id = id
22
-
23
- unpack(data)
24
- end
25
-
26
- def lights
27
- @lights ||= begin
28
- @light_ids.map do |light_id|
29
- @client.light(light_id)
30
- end
31
- end
32
- end
33
-
34
- private
35
-
36
- SCENE_KEYS_MAP = {
37
- :name => :name,
38
- :light_ids => :lights,
39
- :active => :active,
40
- }
41
-
42
- def unpack(data)
43
- unpack_hash(data, SCENE_KEYS_MAP)
44
- end
45
-
46
- def base_url
47
- "http://#{@bridge.ip}/api/#{@client.username}/scenes/#{id}"
48
- end
49
- end
50
- end
@@ -1,21 +0,0 @@
1
- module Hue
2
- module TranslateKeys
3
- def translate_keys(hash, map)
4
- new_hash = {}
5
- hash.each do |key, value|
6
- new_key = map[key.to_sym]
7
- key = new_key if new_key
8
- new_hash[key] = value
9
- end
10
- new_hash
11
- end
12
-
13
- def unpack_hash(hash, map)
14
- map.each do |local_key, remote_key|
15
- value = hash[remote_key.to_s]
16
- next unless value
17
- instance_variable_set("@#{local_key}", value)
18
- end
19
- end
20
- end
21
- end
data/lib/hue/version.rb DELETED
@@ -1,3 +0,0 @@
1
- module Hue
2
- VERSION = '0.1.5'
3
- end
data/lib/hue.rb DELETED
@@ -1,13 +0,0 @@
1
- require 'hue/version'
2
- require 'hue/errors'
3
- require 'hue/client'
4
- require 'hue/bridge'
5
- require 'hue/editable_state'
6
- require 'hue/translate_keys'
7
- require 'hue/light'
8
- require 'hue/group'
9
- require 'hue/scene'
10
-
11
- module Hue
12
- USERNAME_RANGE = 10..40
13
- end