lifx_api 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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +72 -0
- data/Rakefile +15 -0
- data/lib/lifx_api/endpoints.rb +121 -0
- data/lib/lifx_api/error.rb +17 -0
- data/lib/lifx_api/version.rb +3 -0
- data/lib/lifx_api.rb +125 -0
- data/lifx_api.gemspec +25 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 055c814b51eff3591097009aed7385b3022fd6e2
|
4
|
+
data.tar.gz: d66f4c78f22718034ae0de373b357e344730ac80
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3be95ba0e47d8d6e1eb53d2bdd7c557e1f755eab02b9ceea431dc7df6d8bde85a40be32e9afcbebcfde145d6b32a4156005f87113087028ca56b2a9e6cdf5a84
|
7
|
+
data.tar.gz: bbf46d92d4e5362098886b8677778ef24d2bba265f59f11c41ef5b9d2e0a3776ff736f20e05eb53b126944e5d8aad18d60367ff7dfb785dcfa0f34c484a0d7d1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# LifxApi
|
2
|
+
|
3
|
+
A Ruby client for the LIFX API.
|
4
|
+
|
5
|
+
This provides access to the [LIFX HTTP API](https://api.developer.lifx.com/), so it can control your lights from anywhere in the world. It does not implement the [LAN API](https://lan.developer.lifx.com/).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'lifx_api'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install lifx_api
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
You'll need to have some LIFX bulbs already setup and configured. Then you will need to obtain an `access_token` from the [LIFX website](https://cloud.lifx.com/sign_in).
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'lifx_api'
|
29
|
+
|
30
|
+
access_token = "a1b7349df2a...e213"
|
31
|
+
client = LifxApi.new access_token
|
32
|
+
lights = client.list_lights
|
33
|
+
exit() if lights.count == 0
|
34
|
+
light_id = lights.first[:id]
|
35
|
+
|
36
|
+
client.toggle_power selector: 'all'
|
37
|
+
|
38
|
+
client.toggle_power selector: "id:#{light_id}"
|
39
|
+
```
|
40
|
+
|
41
|
+
## Endpoints and parameters
|
42
|
+
|
43
|
+
### TODO. See [LIFX HTTP API](https://api.developer.lifx.com/) in the mean time
|
44
|
+
|
45
|
+
## Deviation from the API spec
|
46
|
+
|
47
|
+
Some API endpoints require a mandatory [`selector`](https://api.developer.lifx.com/docs/selectors) parameter, which defines which bulbs to apply your action to. This client will default the `selector` parameter to `'all`', if no selector is provided.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# which means that you can call
|
51
|
+
client.list_bulbs
|
52
|
+
# ...and receive a hash of all your bulbs back
|
53
|
+
|
54
|
+
# instead of having to explicitly specify you want all bulbs:
|
55
|
+
client.list_bulbs selector: 'all'
|
56
|
+
```
|
57
|
+
|
58
|
+
## Exceptions
|
59
|
+
|
60
|
+
If there is an error, LifxApi will raise an exception. The exception message will usually give a good indication of what went wrong, but you can also rescue the exception and access the request, response and decoded JSON objects, via the `request`, `response` and `data` methods.
|
61
|
+
|
62
|
+
## Development
|
63
|
+
|
64
|
+
Run `rake test` to run the tests and `rake console` to start an interactive pry console.
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cyclotron3k/lifx_api.
|
69
|
+
|
70
|
+
## License
|
71
|
+
|
72
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
desc "Launch an interactive Pry console"
|
13
|
+
task :console do
|
14
|
+
exec "pry -r lifx_api -I ./lib"
|
15
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
class LifxApi
|
2
|
+
ENDPOINTS = [{
|
3
|
+
method_name: :list_lights,
|
4
|
+
http_method: :get,
|
5
|
+
path: '/v1/lights/%{selector}',
|
6
|
+
path_params: {
|
7
|
+
selector: {required: true, type: :selector, default: 'all'},
|
8
|
+
},
|
9
|
+
}, {
|
10
|
+
method_name: :set_state,
|
11
|
+
http_method: :put,
|
12
|
+
path: '/v1/lights/%{selector}/state',
|
13
|
+
path_params: {
|
14
|
+
selector: {required: true, type: :selector, default: 'all'},
|
15
|
+
},
|
16
|
+
body_params: {
|
17
|
+
power: {type: :string, description: 'The power state you want to set on the selector. on or off'},
|
18
|
+
color: {type: :string, description: 'The color to set the light to.'},
|
19
|
+
brightness: {type: :numeric, description: 'The brightness level from 0.0 to 1.0. Overrides any brightness set in color (if any).'},
|
20
|
+
duration: {type: :numeric, default_decription: '1.0', description: 'How long in seconds you want the power action to take. Range: 0.0 - 3155760000.0 (100 years)'},
|
21
|
+
infrared: {type: :numeric, description: 'The maximum brightness of the infrared channel.'},
|
22
|
+
},
|
23
|
+
}, {
|
24
|
+
method_name: :set_states,
|
25
|
+
http_method: :put,
|
26
|
+
path: '/v1/lights/states',
|
27
|
+
body_params: {
|
28
|
+
states: {required: true},
|
29
|
+
defaults: {type: :hash},
|
30
|
+
},
|
31
|
+
}, {
|
32
|
+
method_name: :stage_delta,
|
33
|
+
http_method: :post,
|
34
|
+
path: '/v1/lights/%{selector}/state/delta',
|
35
|
+
path_params: {
|
36
|
+
selector: {required: true, type: :selector, default: 'all'}
|
37
|
+
},
|
38
|
+
body_params: {
|
39
|
+
power: {type: :on_off, description: 'The power state you want to set on the selector. on or off'},
|
40
|
+
duration: {type: :numeric, default_decription: '1.0', description: 'How long in seconds you want the power action to take. Range: 0.0 - 3155760000.0 (100 years)'},
|
41
|
+
infrared: {type: :numeric, description: 'The maximum brightness of the infrared channel.'},
|
42
|
+
hue: {type: :numeric, description: 'Rotate the hue by this angle in degrees.'},
|
43
|
+
saturation: {type: :numeric, description: 'Change the saturation by this additive amount; the resulting saturation is clipped to [0, 1].'},
|
44
|
+
brightness: {type: :numeric, description: 'Change the brightness by this additive amount; the resulting brightness is clipped to [0, 1].'},
|
45
|
+
kelvin: {type: :numeric, description: 'Change the kelvin by this additive amount; the resulting kelvin is clipped to [2500, 9000].'},
|
46
|
+
},
|
47
|
+
}, {
|
48
|
+
method_name: :toggle_power,
|
49
|
+
http_method: :post,
|
50
|
+
path: '/v1/lights/%{selector}/toggle',
|
51
|
+
path_params: {
|
52
|
+
selector: {required: true, type: :selector, default: 'all'},
|
53
|
+
},
|
54
|
+
body_params: {
|
55
|
+
duration: {type: :numeric, default_decription: '1.0', description: 'The time is seconds to spend perfoming the power toggle.'},
|
56
|
+
},
|
57
|
+
}, {
|
58
|
+
method_name: :breathe_effect,
|
59
|
+
http_method: :post,
|
60
|
+
path: '/v1/lights/%{selector}/effects/breathe',
|
61
|
+
path_params: {
|
62
|
+
selector: {required: true, type: :selector, default: 'all'},
|
63
|
+
},
|
64
|
+
body_params: {
|
65
|
+
color: {required: :true, type: :string, description: 'The color to use for the breathe effect.'},
|
66
|
+
from_color: {type: :string, default_decription: 'current bulb color', description: 'The color to start the effect from. If this parameter is omitted then the color the bulb is currently set to is used instead.'},
|
67
|
+
period: {type: :numeric, default_decription: '1.0', description: 'The time in seconds for one cyles of the effect.'},
|
68
|
+
cycles: {type: :numeric, default_decription: '1.0', description: 'The number of times to repeat the effect.'},
|
69
|
+
persist: {type: :boolean, default_decription: 'false', description: 'If false set the light back to its previous value when effect ends, if true leave the last effect color.'},
|
70
|
+
power_on: {type: :boolean, default_decription: 'true', description: 'If true, turn the bulb on if it is not already on.'},
|
71
|
+
peak: {type: :numeric, default_decription: '0.5', description: 'Defines where in a period the target color is at its maximum. Minimum 0.0, maximum 1.0.'},
|
72
|
+
}
|
73
|
+
}, {
|
74
|
+
method_name: :pulse_effect,
|
75
|
+
http_method: :post,
|
76
|
+
path: '/v1/lights/%{selector}/effects/pulse',
|
77
|
+
path_params: {
|
78
|
+
selector: {required: true, type: :selector, default: 'all'},
|
79
|
+
},
|
80
|
+
body_params: {
|
81
|
+
color: {required: true, type: :string, description: 'The color to use for the pulse effect.'},
|
82
|
+
from_color: {type: :string, default_decription: 'current bulb color', description: 'The color to start the effect from. If this parameter is omitted then the color the bulb is currently set to is used instead.'},
|
83
|
+
period: {type: :numeric, default_decription: '1.0', description: 'The time in seconds for one cyles of the effect.'},
|
84
|
+
cycles: {type: :numeric, default_decription: '1.0', description: 'The number of times to repeat the effect.'},
|
85
|
+
persist: {type: :boolean, default_decription: 'false', description: 'If false set the light back to its previous value when effect ends, if true leave the last effect color.'},
|
86
|
+
power_on: {type: :boolean, default_decription: 'true', description: 'If true, turn the bulb on if it is not already on.'},
|
87
|
+
}
|
88
|
+
}, {
|
89
|
+
method_name: :cycle,
|
90
|
+
http_method: :post,
|
91
|
+
path: '/v1/lights/%{selector}/cycle',
|
92
|
+
body_params: {
|
93
|
+
states: {required: true, type: 'array of mixed', description: 'Array of state hashes as per Set State. Must have 2 to 5 entries.'},
|
94
|
+
defaults: {type: 'object', description: 'Default values to use when not specified in each states[] object.'},
|
95
|
+
direction: {type: 'stringforward', description: 'Direction in which to cycle through the list. Can be forward or backward'},
|
96
|
+
}
|
97
|
+
}, {
|
98
|
+
method_name: :list_scenes,
|
99
|
+
http_method: :get,
|
100
|
+
path: '/v1/scenes',
|
101
|
+
}, {
|
102
|
+
method_name: :activate_scene,
|
103
|
+
http_method: :put,
|
104
|
+
path: '/v1/scenes/scene_id:%{scene_uuid}/activate',
|
105
|
+
path_params: {
|
106
|
+
scene_uuid: {required: true, type: :uuid, description: 'The UUID for the scene you wish to activate'},
|
107
|
+
},
|
108
|
+
body_params: {
|
109
|
+
duration: {type: :numeric, default_decription: '1.0', description: 'The time in seconds to spend performing the scene transition.'},
|
110
|
+
ignore: {type: 'array of strings', description: 'Any of "power", "infrared", "duration", "intensity", "hue", "saturation", "brightness" or "kelvin", specifying that these properties should not be changed on devices when applying the scene.'},
|
111
|
+
overrides: {type: 'object', description: 'A state object as per Set State specifying properties to apply to all devices in the scene, overriding those configured in the scene.'},
|
112
|
+
}
|
113
|
+
}, {
|
114
|
+
method_name: :validate_color,
|
115
|
+
http_method: :put,
|
116
|
+
path: '/v1/color',
|
117
|
+
query_params: {
|
118
|
+
color: {type: :string, required: true},
|
119
|
+
},
|
120
|
+
}]
|
121
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class LifxApi
|
2
|
+
class Error < StandardError
|
3
|
+
attr_reader :request, :response, :data
|
4
|
+
|
5
|
+
def initialize(request, response, data=nil)
|
6
|
+
@request = request
|
7
|
+
@response = response
|
8
|
+
@data = data
|
9
|
+
message = if data.is_a?(Hash) and data.key? :error
|
10
|
+
"#{response.code} - #{data[:error]}"
|
11
|
+
else
|
12
|
+
"#{response.code}"
|
13
|
+
end
|
14
|
+
super message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/lifx_api.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'openssl'
|
3
|
+
require 'cgi'
|
4
|
+
require 'json'
|
5
|
+
require 'lifx_api/version'
|
6
|
+
require 'lifx_api/endpoints'
|
7
|
+
require 'lifx_api/error'
|
8
|
+
|
9
|
+
class LifxApi
|
10
|
+
|
11
|
+
PROTOCOL = 'https'
|
12
|
+
HOST = 'api.lifx.com'
|
13
|
+
DEBUG = false
|
14
|
+
|
15
|
+
def initialize(access_token)
|
16
|
+
@access_token = access_token
|
17
|
+
@base_uri = URI "#{PROTOCOL}://#{HOST}"
|
18
|
+
|
19
|
+
@agent = Net::HTTP.new @base_uri.host, @base_uri.port
|
20
|
+
@agent.use_ssl = PROTOCOL == 'https',
|
21
|
+
@agent.keep_alive_timeout = 10
|
22
|
+
@agent.set_debug_output $stdout if DEBUG
|
23
|
+
end
|
24
|
+
|
25
|
+
ENDPOINTS.each do |endpoint_spec|
|
26
|
+
define_method endpoint_spec[:method_name], Proc.new { |params={}|
|
27
|
+
parsed_params = parse_params endpoint_spec, params
|
28
|
+
request = create_request endpoint_spec, parsed_params
|
29
|
+
process_request request
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse_params(endpoint_spec, params)
|
36
|
+
[:path_params, :body_params, :query_params].each_with_object({}) do |field, parsed|
|
37
|
+
next unless endpoint_spec[field]
|
38
|
+
|
39
|
+
parsed[field] = endpoint_spec[field].each_with_object({}) do |(key, field_spec), clean|
|
40
|
+
value = params[key] || field_spec[:default]
|
41
|
+
raise "Missing #{key}" if field_spec[:required] and value.nil?
|
42
|
+
next if value.nil?
|
43
|
+
raise ArgumentError, "#{key} (#{value}) failed validation" unless valid? value, field_spec[:type]
|
44
|
+
clean[key] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid?(value, value_format)
|
50
|
+
case value_format
|
51
|
+
when :selector
|
52
|
+
value =~ /^((label|id|(location|group)(_id)?|scene_id):.*|all)$/
|
53
|
+
when :numeric
|
54
|
+
value.is_a?(Numeric) or value =~ /^[\d\.]+$/
|
55
|
+
when :boolean
|
56
|
+
['true', 'false', true, false].include? value
|
57
|
+
when :on_off
|
58
|
+
['on', 'off'].include? value
|
59
|
+
when :hash
|
60
|
+
value.is_a? Hash
|
61
|
+
when :string
|
62
|
+
value.is_a? String
|
63
|
+
when :uuid
|
64
|
+
value.is_a?(String) and value =~ /^[\da-f]{4}([\da-f]{4}-){4}[\da-f]{12}$/
|
65
|
+
else
|
66
|
+
puts "Don't know how to validate #{value_format}" if DEBUG
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_request(endpoint_spec, params)
|
72
|
+
uri = @base_uri.clone
|
73
|
+
|
74
|
+
uri.path = if params.key? :path_params
|
75
|
+
endpoint_spec[:path] % Hash[params[:path_params].map { |k, v| [k, CGI.escape(v).gsub('+', '%20')]}]
|
76
|
+
else
|
77
|
+
endpoint_spec[:path]
|
78
|
+
end
|
79
|
+
|
80
|
+
if params.key? :query_params
|
81
|
+
uri.query = URI.encode_www_form params[:query_params]
|
82
|
+
end
|
83
|
+
|
84
|
+
request = case endpoint_spec[:http_method]
|
85
|
+
when :get
|
86
|
+
Net::HTTP::Get.new uri
|
87
|
+
when :put
|
88
|
+
Net::HTTP::Put.new uri
|
89
|
+
when :post
|
90
|
+
Net::HTTP::Post.new uri
|
91
|
+
else
|
92
|
+
raise NotImplementedError, "Invalid HTTP method: #{endpoint_spec[:http_method]}"
|
93
|
+
end
|
94
|
+
|
95
|
+
if params.key? :body_params
|
96
|
+
request.set_form_data params[:body_params]
|
97
|
+
end
|
98
|
+
|
99
|
+
request
|
100
|
+
end
|
101
|
+
|
102
|
+
def process_request(request)
|
103
|
+
request['Authorization'] = "Bearer #{@access_token}"
|
104
|
+
|
105
|
+
puts "\n\n\e[1;32mDespatching request to #{request.path}\e[0m" if DEBUG
|
106
|
+
|
107
|
+
@session = @agent.start unless @agent.active?
|
108
|
+
response = @session.request request
|
109
|
+
|
110
|
+
puts "\e[1;32mResponse received\e[0m" if DEBUG
|
111
|
+
|
112
|
+
data = case response['content-type']
|
113
|
+
when /application\/json/
|
114
|
+
JSON.parse response.read_body, symbolize_names: true
|
115
|
+
else
|
116
|
+
raise "Don't know how to parse #{response['content-type']}"
|
117
|
+
end
|
118
|
+
|
119
|
+
unless (200..299).include? response.code.to_i
|
120
|
+
raise LifxApi::Error.new request, response, data
|
121
|
+
end
|
122
|
+
|
123
|
+
data
|
124
|
+
end
|
125
|
+
end
|
data/lifx_api.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "lifx_api/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lifx_api"
|
8
|
+
spec.version = LifxApi::VERSION
|
9
|
+
spec.authors = "cyclotron3k"
|
10
|
+
|
11
|
+
spec.summary = "A client for the LIFX HTTP API"
|
12
|
+
spec.homepage = "https://github.com/cyclotron3k/lifx_api"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lifx_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- cyclotron3k
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.10'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- ".gitignore"
|
76
|
+
- Gemfile
|
77
|
+
- LICENSE.txt
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- lib/lifx_api.rb
|
81
|
+
- lib/lifx_api/endpoints.rb
|
82
|
+
- lib/lifx_api/error.rb
|
83
|
+
- lib/lifx_api/version.rb
|
84
|
+
- lifx_api.gemspec
|
85
|
+
homepage: https://github.com/cyclotron3k/lifx_api
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.5.1
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A client for the LIFX HTTP API
|
109
|
+
test_files: []
|