beebotte 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6a342811e6b3041aacf419ffa54d37bc9ff1904e
4
+ data.tar.gz: ca0f73eda5f8a58aa780dcb913ef52769b413e4c
5
+ SHA512:
6
+ metadata.gz: bbea1488fbed398f5640f684ef9942a5a84f40bbac798685cd53de98ae24e62cebae1f24b45d4f03e53fef040eac57e393a824fa4f89500506bbefd308c5bcf2
7
+ data.tar.gz: fa588fa2d9acac1a8a5acfc220c69bb741afb5c726134a2f823363998933c5a08b59851e765072f1ab2e035de60e51bdadd6b37730ce80ed69d0a692aef1145d
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea
11
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in beebotte.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard 'rspec', cmd: "bundle exec rspec" do
2
+ # watch /lib/ files
3
+ watch(%r{^lib/(.+).rb$}) do |m|
4
+ "spec/#{m[1]}_spec.rb"
5
+ end
6
+
7
+ # watch /spec/ files
8
+ watch(%r{^spec/(.+).rb$}) do |m|
9
+ "spec/#{m[1]}.rb"
10
+ end
11
+ end
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ Beebotte ruby gem
2
+ =================
3
+
4
+
5
+ THIS IS ALPHA SOFTWARE - USE AT YOUR OWN PERIL
6
+
7
+ Example usage:
8
+ --------------
9
+ ```
10
+ b = Beebotte::Connector.new("<yourApiKey>", "<yourSecretKey>", 'api.beebotte.com', 443)
11
+
12
+ channel_name = SecureRandom.hex(8)
13
+ resource_name = SecureRandom.hex(8)
14
+ resource_name2 = SecureRandom.hex(8)
15
+
16
+ puts "\n\n---------\nadd_channels:"
17
+ channel = {"name":channel_name, "resources": [{"name":resource_name, "vtype":"any"}]}
18
+ b.add_channel(channel) {|r, code| puts "(#{code}) #{r.inspect}" }
19
+
20
+ puts "\n\n---------\nwrite:"
21
+ b.write(channel_name, resource_name, { id: rand(1000000), status:"A sample write message"})
22
+
23
+ puts "\n\n---------\npublish:"
24
+ b.publish(channel_name, resource_name, { id: rand(1000000), status:"A sample publish message"})
25
+
26
+ puts "\n\n---------\nread:"
27
+ b.read({channel: channel_name, resource: resource_name, limit: 1}) {|r, code| puts "(#{code}) #{r.inspect}"}
28
+
29
+
30
+ puts "\n\n---------\nadd_resource:"
31
+ resource2 = {"name":resource_name2, "vtype":"any"}
32
+ b.add_resource(channel_name, resource2) {|r, code| puts "(#{code}) #{r.inspect}"}
33
+
34
+ puts "\n\n---------\ndel_resource:"
35
+ b.del_resource(channel_name, resource_name2) {|r, code| puts "(#{code}) #{r.inspect}"}
36
+
37
+ puts "\n\n---------\nget_channels:"
38
+ b.get_channels {|r, code| puts "(#{code}) #{r.inspect}"}
39
+
40
+ puts "\n\n---------\nget_channel:"
41
+ b.get_channel(channel_name) {|r, code| puts "(#{code}) #{r.inspect}"}
42
+
43
+ puts "\n\n---------\nget_connections:"
44
+ b.get_connections {|r, code| puts "(#{code}) #{r.inspect}"}
45
+
46
+ puts "\n\n---------\nget_resources:"
47
+ b.get_resources(resource_name) {|r, code| puts "(#{code}) #{r.inspect}"}
48
+
49
+
50
+ puts "\n\n---------\ndel_channel:"
51
+ b.del_channel(channel_name) {|r, code| puts "(#{code}) #{r.inspect}"}
52
+
53
+ ```
54
+
55
+ TODO:
56
+ -----
57
+ 1. Documentation
58
+ 1. Testing
59
+ 1. Token authentication for REST API
60
+ 1. Bulk API
61
+ 1. Stream API
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '--format', 'nested']
8
+ end
9
+
10
+ task :default => :spec
data/beebotte.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'beebotte/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "beebotte"
8
+ spec.version = Beebotte::VERSION
9
+ spec.authors = ["Mike Kazmier"]
10
+ spec.email = ["dakazmier@gmail.com"]
11
+
12
+ spec.summary = "Beebotte REST API connector"
13
+ spec.description = "A pure ruby implementation of the BBT connector for beebotte's REST api"
14
+ spec.homepage = "https://github.com/DaKaZ/bbt_ruby"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_runtime_dependency 'openssl'
24
+ spec.add_runtime_dependency 'json'
25
+ spec.add_runtime_dependency 'rest-client', '>= 2.0.0'
26
+ spec.add_runtime_dependency 'classy_hash'
27
+ spec.add_runtime_dependency 'mqtt'
28
+ spec.add_development_dependency "rspec"
29
+ spec.add_development_dependency "rspec-nc"
30
+ spec.add_development_dependency "guard"
31
+ spec.add_development_dependency "guard-rspec"
32
+ spec.add_development_dependency "pry"
33
+ spec.add_development_dependency "webmock"
34
+ spec.add_development_dependency "bundler", "~> 1.14"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "beebotte"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/beebotte.rb ADDED
@@ -0,0 +1,345 @@
1
+ require "beebotte/version"
2
+
3
+ module Beebotte
4
+ require 'openssl'
5
+ require 'json'
6
+ require 'rest-client'
7
+ require 'base64'
8
+ require 'classy_hash'
9
+ require 'mqtt'
10
+
11
+ class Connector
12
+
13
+ ATTRIBUTE_TYPE_LABELS = [
14
+ 'any',
15
+ 'number',
16
+ 'string',
17
+ 'boolean',
18
+ 'object',
19
+ 'function',
20
+ 'array',
21
+ 'alphabetic',
22
+ 'alphanumeric',
23
+ 'decimal',
24
+ 'rate',
25
+ 'percentage',
26
+ 'email',
27
+ 'gps',
28
+ 'cpu',
29
+ 'memory',
30
+ 'netif',
31
+ 'disk',
32
+ 'temperature',
33
+ 'humidity',
34
+ 'body_temp',
35
+ ]
36
+
37
+ def initialize(apiKey, secretKey, hostname='api.beebotte.com', port=80, protocol=nil, headers=nil)
38
+ @apiKey = apiKey
39
+ @secretKey = secretKey
40
+ @hostname = hostname
41
+ @port = port
42
+ @protocol = protocol.is_a?(String) ? protocol.downcase : ((@port == 443) ? 'https' : 'http')
43
+ @headers = headers || {
44
+ "Content-type" => 'application/json',
45
+ "Content-MD5" => '',
46
+ "User-Agent" => get_useragent_string
47
+ }
48
+
49
+ @resource_schema = {
50
+ name: CH::G.string_length(2..30),
51
+ label: [:optional, CH::G.string_length(0..30) ],
52
+ description: [:optional, String ],
53
+ vtype: Set.new(ATTRIBUTE_TYPE_LABELS),
54
+ sendOnSubscribe: [:optional, TrueClass]
55
+ }
56
+
57
+ @channel_schema = {
58
+ name: CH::G.string_length(2..30),
59
+ label: [:optional, CH::G.string_length(0..30) ],
60
+ description: [:optional, String ],
61
+ resources: [ [ @resource_schema ] ],
62
+ ispublic: [:optional, TrueClass]
63
+ }
64
+
65
+ @read_params_schema = {
66
+ channel: CH::G.string_length(2..30),
67
+ resource: CH::G.string_length(2..30),
68
+ limit: [:optional, 1..2000 ],
69
+ 'time-range': [:optional, String],
70
+ 'start-time': [:optional, String],
71
+ 'end-time': [:optional, String],
72
+ filter: [:optional, String],
73
+ 'sample-rate': [:optional, 1..10000]
74
+ }
75
+
76
+ end
77
+
78
+ # write persistent information
79
+ def write(channel, resource, data, &block)
80
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
81
+ raise ArgumentError, 'Resource name must be a string' unless resource.is_a?(String)
82
+ raise ArgumentError, 'Data must be a hash object' unless data.is_a?(Hash)
83
+
84
+ body = {data: data}
85
+ response = post_data("/v1/data/write/#{channel}/#{resource}", body.to_json)
86
+ block.call(response.body, response.code) if block
87
+ end
88
+
89
+ # publish transient information
90
+ def publish(channel, resource, data, &block)
91
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
92
+ raise ArgumentError, 'Resource name must be a string' unless resource.is_a?(String)
93
+ raise ArgumentError, 'Data must be a hash object' unless data.is_a?(Hash)
94
+ body = {data: data}
95
+ response = post_data("/v1/data/publish/#{channel}/#{resource}", body.to_json)
96
+ block.call(response.body, response.code) if block
97
+ end
98
+
99
+ # Read persisted messages from the specified channel and resource
100
+ #
101
+ # ==== Attributes
102
+ #
103
+ # * +channel+ - String: the channel name
104
+ # * +resource+ - String: the resource name
105
+ # * +params+ - Hash: the query parameters: 'time-range', 'start-time', 'end-time', 'limit', 'filter', 'sample-rate'
106
+ #
107
+ def read(params, &block)
108
+ ClassyHash.validate(params, @read_params_schema, strict: true)
109
+ params[:limit] ||= 750
110
+ rtn = {}
111
+ uri = "/v1/data/read/#{params[:channel]}/#{params[:resource]}"
112
+ [:channel, :resource].each {|k| params.delete(k) }
113
+ puts "PARAMS: #{params.inspect}"
114
+ response = get_data(uri, params)
115
+ rtn = JSON.parse(response.body) if response.code >= 200 && response.code < 300
116
+ block.call(rtn, response.code) if block
117
+ end
118
+
119
+ def get_connections(user=nil, &block)
120
+ resource = user.nil? ? [] : {}
121
+ uri = "/v1/connections" + (resource.is_a?(String) ? "/#{resource}" : '')
122
+ response = get_data(uri)
123
+ resource = JSON.parse(response.body) if response.code >= 200 && response.code < 300
124
+ block.call(resource, response.code) if block
125
+ end
126
+
127
+ def get_conection(user, &block)
128
+ raise ArgumentError, 'User name must be a string' unless user.is_a?(String)
129
+ get_connections(user, &block)
130
+ end
131
+
132
+
133
+ # get_channels { |response| puts response.body }
134
+ def get_channels(channel=nil, &block)
135
+ rtn = {}
136
+ uri = "/v1/channels" + (channel.is_a?(String) ? "/#{channel}" : '')
137
+ response = get_data(uri)
138
+ rtn = JSON.parse(response.body) if response.code >= 200 && response.code < 300
139
+ block.call(rtn, response.code) if block
140
+ end
141
+
142
+ def get_channel(channel, &block)
143
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
144
+ get_channels(channel, &block)
145
+ end
146
+
147
+ def add_channel(channel, &block)
148
+ ClassyHash.validate(channel, @channel_schema, strict: true)
149
+ # validate that no resource descriptions are the same as the channel name
150
+ raise ArgumentError, 'Must have at least one resource' if channel[:resources].length < 1
151
+ channel[:resources].each do |r|
152
+ raise ArgumentError, 'Resource :name must not equal Channel :name' if r[:name] == channel[:name]
153
+ end
154
+ response = post_data("/v1/channels", channel.to_json)
155
+ if response.code >= 200 && response.code < 300
156
+ get_channel(channel[:name], &block)
157
+ else
158
+ block.call({}, response.code) if block
159
+ end
160
+ end
161
+
162
+ def del_channel(channel, &block)
163
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
164
+ response = del_data("/v1/channels/#{channel}")
165
+ block.call(response.body, response.code) if block
166
+ end
167
+
168
+ def get_resources(channel, resource='*', &block)
169
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
170
+ rtn = {}
171
+ params = {
172
+ resource: resource
173
+ }
174
+ response = get_data("/v1/channels/#{channel}/resources", params)
175
+ rtn = JSON.parse(response.body) if response.code >= 200 && response.code < 300
176
+ block.call(rtn, response.code) if block
177
+ end
178
+
179
+ def get_resource(channel, resource, &block)
180
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
181
+ raise ArgumentError, 'Resource name must be a string' unless resource.is_a?(String)
182
+ get_resources(channel, resource, &block)
183
+ end
184
+
185
+ #
186
+ # {resource: {name, description, type, vtype, ispublic}}
187
+ #
188
+ def add_resource(channel, resource, &block)
189
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
190
+ ClassyHash.validate(resource, @resource_schema, strict: true)
191
+ # validate that no resource descriptions are the same as the channel name
192
+ raise ArgumentError, 'Resource :name must not equal Channel :name' if resource[:name] == channel
193
+ response = post_data("/v1/channels/#{channel}/resources", resource.to_json)
194
+ if response.code >= 200 && response.code < 300
195
+ get_resource(channel, resource[:name], &block)
196
+ else
197
+ block.call({}, response.code) if block
198
+ end
199
+ end
200
+
201
+ def del_resource(channel, resource, &block)
202
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String)
203
+ raise ArgumentError, 'Resource name must be a string' unless resource.is_a?(String)
204
+ response = del_data("/v1/channels/#{channel}/resources/#{resource}")
205
+ block.call(response.body, response.code) if block
206
+ end
207
+
208
+ private
209
+
210
+ def get_data(uri, params=nil)
211
+ @headers["Content-MD5"] = ''
212
+ if params && params.is_a?(Hash)
213
+ params.each_with_index do |a, index|
214
+ uri << ((index == 0) ? "?" : "&")
215
+ uri << "#{a[0]}=#{a[1]}"
216
+ end
217
+ end
218
+ signature = get_signature('GET', uri, @headers, @secretKey)
219
+ @headers["Authorization"] = "#{@apiKey}:#{signature}"
220
+ url = "#{@protocol}://#{@hostname}:#{@port}#{uri}"
221
+ puts "URL: #{url}"
222
+ response = RestClient.get(url, @headers)
223
+ end
224
+
225
+ def post_data(uri, body=nil)
226
+ @headers["Content-MD5"] = body.nil? ? '' : Digest::MD5.base64digest(body)
227
+ signature = get_signature('POST', uri, @headers, @secretKey)
228
+ @headers["Authorization"] = "#{@apiKey}:#{signature}"
229
+ url = "#{@protocol}://#{@hostname}:#{@port}#{uri}"
230
+ puts "URL: #{url}"
231
+ puts "BODY: #{body}"
232
+ response = RestClient.post(url, body, @headers)
233
+ end
234
+
235
+ def del_data(uri)
236
+ @headers["Content-MD5"] = ''
237
+ signature = get_signature('DELETE', uri, @headers, @secretKey)
238
+ @headers["Authorization"] = "#{@apiKey}:#{signature}"
239
+ url = "#{@protocol}://#{@hostname}:#{@port}#{uri}"
240
+ puts "URL: #{url}"
241
+ response = RestClient.delete(url, @headers)
242
+ end
243
+
244
+ def get_signature(method, path, headers, secretKey)
245
+ @headers["Date"] = Time.now.utc.httpdate
246
+ raise ArgumentError, 'Beebotte Secret key is missing' if secretKey.nil? || !secretKey.is_a?(String)
247
+ raise ArgumentError, 'Invalid method' unless (method == 'GET' || method == 'PUT' || method == 'POST' || method == 'DELETE')
248
+ raise ArgumentError, 'Invalid path' unless path.is_a?(String) && path.match(/^\//)
249
+ stringToSign = "#{method}\n#{headers['Content-MD5']}\n#{headers["Content-type"]}\n#{headers["Date"]}\n#{path}"
250
+ puts "stiringToSign= \"#{stringToSign}\""
251
+ signature = sha1_sign(secretKey, stringToSign)
252
+ end
253
+
254
+ def sha1_sign(secretKey, stringToSign)
255
+ digest = OpenSSL::Digest.new('sha1')
256
+ hmac = OpenSSL::HMAC.digest(digest, secretKey, stringToSign)
257
+ signature = Base64.strict_encode64(hmac)
258
+ end
259
+
260
+ def get_useragent_string
261
+ return "beebotte ruby SDK v#{Beebotte::VERSION}"
262
+ end
263
+ end
264
+
265
+ class Stream
266
+ attr_accessor :token, :host, :port, :ssl
267
+ attr_reader :subsciptions
268
+
269
+ class NotConnected < StandardError; end
270
+ class NoSubscriptions < StandardError; end
271
+
272
+ def initialize(opts = {})
273
+ @token = opts[:token]
274
+ @apiKey = opts[:api_key]
275
+ @secretKey = opts[:secret_key]
276
+ raise ArguementError, 'Must set token OR api_key and secret key' if (@token.nil? && (@apiKey.nil? || @secretKey.nil?))
277
+ @host = opts[:host] || "mqtt.beebotte.com"
278
+ @port = opts[:port] || 1883
279
+ @ssl = opts[:ssl] || false
280
+ @subscrptions = []
281
+ @client = MQTT::Client.new
282
+ end
283
+
284
+ def connect
285
+ @client.disconnect() if @client.connected?
286
+ @client.host = @host
287
+ @client.port = @port
288
+ @client.ssl = @ssl
289
+ @client.username = @client.password = nil
290
+ @subscriptions = []
291
+ if @token
292
+ @client.username = "token:#{@token}"
293
+ end
294
+ @client.connect()
295
+ @client.connected?
296
+ end
297
+
298
+ def disconnect
299
+ @subscriptions = []
300
+ @client.disconnect()
301
+ end
302
+
303
+ def connected?
304
+ @client.connected?
305
+ end
306
+
307
+ def get(&block)
308
+ raise NoSubscriptions if @subscriptions.length == 0
309
+ @client.get(&block)
310
+ end
311
+
312
+ def publish(channel, resource, data)
313
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String) && channel.length.between?(2,30)
314
+ raise ArgumentError, 'Resource name must be a string' unless resource.is_a?(String) && resource.length.between?(2,30)
315
+ raise ArgumentError, 'Data name must be a Hash' unless data.is_a?(Hash)
316
+ data = {data: data, write: false }
317
+ @client.publish("#{channel}/#{resource}" , data.to_json)
318
+ end
319
+
320
+
321
+ def write(channel, resource, data)
322
+ raise ArgumentError, 'Channel name must be a string' unless channel.is_a?(String) && channel.length.between?(2,30)
323
+ raise ArgumentError, 'Resource name must be a string' unless resource.is_a?(String) && resource.length.between?(2,30)
324
+ raise ArgumentError, 'Data name must be a Hash' unless data.is_a?(Hash)
325
+ data = {data: data, write: true }
326
+ @client.publish("#{channel}/#{resource}" , data.to_json)
327
+
328
+ end
329
+
330
+ def subscribe(topic)
331
+ raise ArgumentError, "Topic must be in the form of 'channel/resource'" unless topic.is_a?(String) && topic.match(/^[a-zA-Z0-9_]*\/[a-zA-Z0-9_]*$/)
332
+ raise NotConnected unless connected?
333
+ return true if @subscriptions.include?(topic)
334
+ if @client.subscribe(topic)
335
+ @subscriptions << topic
336
+ return true
337
+ else
338
+ return false
339
+ end
340
+ end
341
+
342
+
343
+ end
344
+ end
345
+
@@ -0,0 +1,3 @@
1
+ module Beebotte
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,235 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: beebotte
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Kazmier
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: openssl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rest-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: classy_hash
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mqtt
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-nc
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: guard-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: bundler
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '1.14'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '1.14'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rake
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '10.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '10.0'
195
+ description: A pure ruby implementation of the BBT connector for beebotte's REST api
196
+ email:
197
+ - dakazmier@gmail.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - ".gitignore"
203
+ - Gemfile
204
+ - Guardfile
205
+ - README.md
206
+ - Rakefile
207
+ - beebotte.gemspec
208
+ - bin/console
209
+ - bin/setup
210
+ - lib/beebotte.rb
211
+ - lib/beebotte/version.rb
212
+ homepage: https://github.com/DaKaZ/bbt_ruby
213
+ licenses: []
214
+ metadata: {}
215
+ post_install_message:
216
+ rdoc_options: []
217
+ require_paths:
218
+ - lib
219
+ required_ruby_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ required_rubygems_version: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ requirements: []
230
+ rubyforge_project:
231
+ rubygems_version: 2.6.11
232
+ signing_key:
233
+ specification_version: 4
234
+ summary: Beebotte REST API connector
235
+ test_files: []