beebotte 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 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: []