huebot 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/hue/bridge.rb DELETED
@@ -1,140 +0,0 @@
1
- module Hue
2
- class Bridge
3
- # ID of the bridge.
4
- attr_reader :id
5
-
6
- # Name of the bridge. This is also its uPnP name, so will reflect the
7
- # actual uPnP name after any conflicts have been resolved.
8
- attr_accessor :name
9
-
10
- # IP address of the bridge.
11
- attr_reader :ip
12
-
13
- # MAC address of the bridge.
14
- attr_reader :mac_address
15
-
16
- # IP Address of the proxy server being used.
17
- attr_reader :proxy_address
18
-
19
- # Port of the proxy being used by the bridge. If set to 0 then a proxy is
20
- # not being used.
21
- attr_reader :proxy_port
22
-
23
- # Software version of the bridge.
24
- attr_reader :software_version
25
-
26
- # Contains information related to software updates.
27
- attr_reader :software_update
28
-
29
- # An array of whitelisted user IDs.
30
- attr_reader :ip_whitelist
31
-
32
- # Network mask of the bridge.
33
- attr_reader :network_mask
34
-
35
- # Gateway IP address of the bridge.
36
- attr_reader :gateway
37
-
38
- # Whether the IP address of the bridge is obtained with DHCP.
39
- attr_reader :dhcp
40
-
41
- def initialize(client, hash)
42
- @client = client
43
- unpack(hash)
44
- end
45
-
46
- # Current time stored on the bridge.
47
- def utc
48
- json = get_configuration
49
- DateTime.parse(json['utc'])
50
- end
51
-
52
- # Indicates whether the link button has been pressed within the last 30
53
- # seconds.
54
- def link_button_pressed?
55
- json = get_configuration
56
- json['linkbutton']
57
- end
58
-
59
- # This indicates whether the bridge is registered to synchronize data with a
60
- # portal account.
61
- def has_portal_services?
62
- json = get_configuration
63
- json['portalservices']
64
- end
65
-
66
- def refresh
67
- json = get_configuration
68
- unpack(json)
69
- @lights = nil
70
- @groups = nil
71
- @scenes = nil
72
- end
73
-
74
- def lights
75
- @lights ||= begin
76
- json = JSON(Net::HTTP.get(URI.parse(base_url)))
77
- json['lights'].map do |key, value|
78
- Light.new(@client, self, key, value)
79
- end
80
- end
81
- end
82
-
83
- def add_lights
84
- uri = URI.parse("#{base_url}/lights")
85
- http = Net::HTTP.new(uri.host)
86
- response = http.request_post(uri.path, nil)
87
- (response.body).first
88
- end
89
-
90
- def groups
91
- @groups ||= begin
92
- json = JSON(Net::HTTP.get(URI.parse("#{base_url}/groups")))
93
- json.map do |id, data|
94
- Group.new(@client, self, id, data)
95
- end
96
- end
97
- end
98
-
99
- def scenes
100
- @scenes ||= begin
101
- json = JSON(Net::HTTP.get(URI.parse("#{base_url}/scenes")))
102
- json.map do |id, data|
103
- Scene.new(@client, self, id, data)
104
- end
105
- end
106
- end
107
-
108
- private
109
-
110
- KEYS_MAP = {
111
- :id => :id,
112
- :ip => :internalipaddress,
113
- :name => :name,
114
- :proxy_port => :proxyport,
115
- :software_update => :swupdate,
116
- :ip_whitelist => :whitelist,
117
- :software_version => :swversion,
118
- :proxy_address => :proxyaddress,
119
- :mac_address => :macaddress,
120
- :network_mask => :netmask,
121
- :portal_services => :portalservices,
122
- }
123
-
124
- def unpack(hash)
125
- KEYS_MAP.each do |local_key, remote_key|
126
- value = hash[remote_key.to_s]
127
- next unless value
128
- instance_variable_set("@#{local_key}", value)
129
- end
130
- end
131
-
132
- def get_configuration
133
- JSON(Net::HTTP.get(URI.parse("#{base_url}/config")))
134
- end
135
-
136
- def base_url
137
- "http://#{ip}/api/#{@client.username}"
138
- end
139
- end
140
- end
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("https://www.meethue.com/api/nupnp"))
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