particlerb 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +210 -32
- data/lib/particle/client/devices.rb +7 -6
- data/lib/particle/client/publish.rb +38 -0
- data/lib/particle/client/tokens.rb +101 -0
- data/lib/particle/client/webhooks.rb +66 -0
- data/lib/particle/client.rb +12 -2
- data/lib/particle/configurable.rb +1 -1
- data/lib/particle/connection.rb +16 -11
- data/lib/particle/device.rb +10 -17
- data/lib/particle/error.rb +2 -0
- data/lib/particle/event.rb +10 -0
- data/lib/particle/model.rb +49 -0
- data/lib/particle/token.rb +76 -0
- data/lib/particle/version.rb +1 -1
- data/lib/particle/webhook.rb +62 -0
- data/lib/particle.rb +19 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3df82a4b9e831fa635c52d24e9d0f59bd3b6209d
|
4
|
+
data.tar.gz: 61c6aa2eba790faeee99e4cde8ea6f04e214946b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a4f275a4d1b7b1dde368e620a76205e4ee34fd23aaca4682d3df0bee6c3407786504d05b648cfe56ddb8ea0e88404a69d5a3479705e58ee6e9eb2276cb5f1d5
|
7
|
+
data.tar.gz: 95e421509a8109498915efcb25096969204afb0fb673a38edca51957ea20874d784a9dca23383e5d6cce600bb65e429f0ad7b8048b8de06985eb09b7c8d6d32f
|
data/README.md
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
|
1
|
+
## particlerb ##
|
2
2
|
|
3
|
-
|
3
|
+
[](http://badge.fury.io/rb/particlerb)
|
4
|
+
[](https://travis-ci.org/monkbroc/particlerb)
|
5
|
+
[](https://codeclimate.com/github/monkbroc/particlerb)
|
6
|
+
|
7
|
+
Ruby client for the [Particle.io] Cloud API with an object-oriented interface
|
4
8
|
|
5
9
|
[Particle.io]: https://www.particle.io
|
6
10
|
|
7
11
|
*Note: this is not an official gem by Particle. It is maintained by Julien Vanier.*
|
8
12
|
|
9
|
-
##
|
13
|
+
## Installation
|
10
14
|
|
11
15
|
Install via Rubygems
|
12
16
|
|
@@ -14,82 +18,111 @@ Install via Rubygems
|
|
14
18
|
|
15
19
|
... or add to your Gemfile
|
16
20
|
|
17
|
-
gem "particlerb", "~> 0.0.
|
21
|
+
gem "particlerb", "~> 0.0.2"
|
18
22
|
|
19
23
|
|
20
|
-
###
|
24
|
+
### Providing credentials
|
21
25
|
|
22
|
-
API
|
23
|
-
configuration) or as client instance methods.
|
26
|
+
**A Particle cloud API access token is necessary for most requests.** You can use the one from the [Web IDE][] for testing, but it's recommended to generate a new token with this gem using `Particle.login` or with the [Particle CLI][] using `particle token new`
|
24
27
|
|
25
28
|
```ruby
|
26
|
-
# Provide
|
29
|
+
# Provide acess token as an environment variable
|
30
|
+
ENV['PARTICLE_ACCESS_TOKEN']
|
31
|
+
|
32
|
+
# Or configure global authentication credentials
|
33
|
+
# If you use Rails, you can put this in config/initializers/particle.rb
|
27
34
|
Particle.configure do |c|
|
28
35
|
c.access_token = "38bb7b318cc6898c80317decb34525844bc9db55"
|
29
36
|
end
|
30
37
|
|
31
|
-
#
|
32
|
-
Particle.
|
38
|
+
# Or pass access token when creating a client
|
39
|
+
# If no token is passed to Particle::Client.new, the global or environment one is used
|
40
|
+
client = Particle::Client.new(access_token: "38bb7b318cc6898c80317decb34525844bc9db55")
|
33
41
|
```
|
34
|
-
|
42
|
+
|
43
|
+
### Making requests
|
44
|
+
|
45
|
+
API methods are available as module methods (consuming module-level
|
46
|
+
configuration) or as client instance methods.
|
35
47
|
|
36
48
|
```ruby
|
37
|
-
#
|
38
|
-
|
49
|
+
# Fetch the list of devices using the global client
|
50
|
+
Particle.devices
|
51
|
+
|
52
|
+
# Or used a newly created client
|
53
|
+
client = Particle::Client.new
|
39
54
|
# Fetch the list of devices
|
40
55
|
client.devices
|
41
56
|
```
|
42
57
|
|
43
|
-
|
58
|
+
[Web IDE]: http://docs.particle.io/core/build/#flash-apps-with-particle-build-account-information
|
59
|
+
[Particle CLI]: http://docs.particle.io/core/cli
|
60
|
+
|
61
|
+
## Interacting with devices
|
44
62
|
|
45
|
-
|
63
|
+
List all devices. Rreturns an `Array` of `Particle::Device`.
|
46
64
|
|
47
|
-
List all devices (returns an `Array` of `Device`)
|
48
65
|
```ruby
|
49
66
|
devices = Particle.devices
|
50
67
|
```
|
51
68
|
|
52
|
-
Get a `Device` by id or name
|
69
|
+
Get a `Particle::Device` by id or name.
|
70
|
+
|
53
71
|
```ruby
|
54
72
|
device = Particle.device('blue_fire')
|
55
73
|
device = Particle.device('f8bbe1e6e69e05c9c405ba1ca504d438061f1b0d')
|
56
74
|
```
|
57
75
|
|
58
|
-
Get information about a device
|
76
|
+
Get information about a device.
|
77
|
+
|
59
78
|
```ruby
|
60
79
|
device = Particle.device('blue_fire')
|
61
|
-
device.id
|
62
|
-
device.name
|
63
|
-
device.connected?
|
64
|
-
device.variables
|
65
|
-
device.functions
|
66
80
|
device.attributes # Hash of all attributes
|
81
|
+
device.id # ==> 'f8bbe1e6e69e05c9c405ba1ca504d438061f1b0d'
|
82
|
+
device.name # ==> 'blue_fire'
|
83
|
+
device.connected? # true
|
84
|
+
device.variables # {:myvar => "double" } or nil if not connected
|
85
|
+
device.functions # ["myfunction"] or nil if not connected
|
67
86
|
device.get_attributes # forces refresh of all attributes from the cloud
|
87
|
+
|
88
|
+
# If you get a Device from the Particle.devices call, you will need to call
|
89
|
+
# get_attributes to get the list of functions and variables since that's not
|
90
|
+
# returned by the cloud when calling Particle.devices
|
91
|
+
device = Particle.devices.first
|
92
|
+
device.connected? # ==> true
|
93
|
+
device.functions # ==> nil
|
94
|
+
device.get_attributes
|
95
|
+
device.functions # ==> ["myfunction"]
|
68
96
|
```
|
69
97
|
|
70
|
-
Claim a device and add it to your account
|
98
|
+
Claim a device by id and add it to your account. Returns a `Particle::Device`.
|
99
|
+
|
71
100
|
```ruby
|
72
|
-
Particle.device('
|
101
|
+
Particle.device('f8bbe1e6e69e05c9c405ba1ca504d438061f1b0d').claim
|
73
102
|
```
|
74
103
|
|
75
|
-
Remove a device from your account
|
104
|
+
Remove a device from your account. Returns true on success.
|
105
|
+
|
76
106
|
```ruby
|
77
107
|
Particle.device('blue_fire').remove
|
78
108
|
Particle.devices.first.remove
|
79
109
|
```
|
80
110
|
|
81
|
-
Rename a device
|
111
|
+
Rename a device. Returns true on success.
|
112
|
+
|
82
113
|
```ruby
|
83
114
|
Particle.device('red').rename('green')
|
84
115
|
```
|
85
116
|
|
86
|
-
Call a function on the firmware
|
117
|
+
Call a function on the firmware with an optional `String` argument. Returns the result of running the function as as `Number`.
|
118
|
+
|
87
119
|
```ruby
|
88
|
-
Particle.device('coffeemaker').function('brew')
|
120
|
+
Particle.device('coffeemaker').function('brew')
|
89
121
|
Particle.devices.first.function('digitalWrite', '1')
|
90
122
|
```
|
91
123
|
|
92
|
-
Get the value of a firmware variable
|
124
|
+
Get the value of a firmware variable. Returns the result as a `String` or `Number`.
|
125
|
+
|
93
126
|
```ruby
|
94
127
|
Particle.device('mycar').variable('battery') # ==> 12.33
|
95
128
|
device = Particle.device('f8bbe1e6e69e05c9c405ba1ca504d438061f1b0d')
|
@@ -97,13 +130,145 @@ device.variable('version') # ==> "1.0.1"
|
|
97
130
|
```
|
98
131
|
|
99
132
|
|
100
|
-
Signal a device to start blinking the RGB LED in rainbow patterns.
|
133
|
+
Signal a device to start blinking the RGB LED in rainbow patterns. Returns whether the device is signaling.
|
134
|
+
|
101
135
|
```ruby
|
102
136
|
Particle.device('nyan_cat').signal(true)
|
103
137
|
```
|
138
|
+
See the [Particle Cloud API documentation about devices][device docs] for more details.
|
139
|
+
|
140
|
+
[device docs]: http://docs.particle.io/core/api/#introduction-device-information
|
141
|
+
|
142
|
+
## Interacting with events
|
143
|
+
|
144
|
+
Publish an event to your devices. Returns true on success.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
Particle.publish(name: "wakeup")
|
148
|
+
Particle.publish(name: "server_ip", data: "8.8.8.8", ttl: 3600, private: true)
|
149
|
+
```
|
150
|
+
|
151
|
+
Data, ttl and private are optional.
|
152
|
+
|
153
|
+
Data is converted to JSON if it is a Hash or an Array, otherwise it is converted to a String.
|
154
|
+
|
155
|
+
See the [Particle Cloud API documentation about publishing events][publish docs] for more details.
|
156
|
+
|
157
|
+
[publish docs]: http://docs.particle.io/core/api/#publishing-events
|
158
|
+
|
159
|
+
### Limitation: Subscribe not supported
|
160
|
+
|
161
|
+
This gem does not support subscribing (listening) to events from devices.
|
162
|
+
|
163
|
+
This would require an HTTP client that supports streaming responses which is not common in Ruby. Some clients like EM-HTTP-Request do support streaming responses, but are tied to specific architectures like EventMachine.
|
164
|
+
|
165
|
+
For web server applications, webhooks are better suited to process incoming events.
|
166
|
+
|
167
|
+
## Interacting with webhooks
|
168
|
+
|
169
|
+
List existing webhooks. Returns an `Array` of `Particle::Webhook`
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
Particle.webhooks
|
173
|
+
```
|
174
|
+
|
175
|
+
Get info about an existing webhook by id. Returns a `Particle::Webhook`
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
webhook = Particle.webhook('ffcddbd30b860ea3cadd22db')
|
179
|
+
webhook.attributes
|
180
|
+
webhook.event
|
181
|
+
webhook.url
|
182
|
+
```
|
183
|
+
|
184
|
+
Calling `attributes` will also send a test message to your webhook url and report the `response` or `error`.
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
webhook.response
|
188
|
+
webhook.error
|
189
|
+
|
190
|
+
webhook = Particle.webhooks.first
|
191
|
+
webhook.get_attributes # force reloading attributes from the cloud
|
192
|
+
# get_attributes necessary to get the response when Webhook was returned from the
|
193
|
+
# Particle.webhooks() method as it doesn't do a test message on each webhook
|
194
|
+
webhook.response
|
195
|
+
```
|
196
|
+
|
197
|
+
Create a new webhook. Pass a hash of [any options accepted by the Particle Cloud API][webhook options]. Returns a `Particle::Webhook`
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
Particle.webhook(event: "weather", url: "http://myserver.com/report").create
|
201
|
+
```
|
202
|
+
See the [Particle Cloud API documentation about webhooks][webhook docs] for more details.
|
203
|
+
|
204
|
+
[webhook docs]: http://docs.particle.io/core/webhooks/
|
205
|
+
[webhook options]: http://docs.particle.io/core/webhooks/#webhook-options
|
206
|
+
|
207
|
+
## Authentication
|
208
|
+
|
209
|
+
Replace the access token on a client
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
Particle.access_token = 'f1d52ea0de921fad300027763d8c5ebd03b1934d'
|
213
|
+
|
214
|
+
# On client instance
|
215
|
+
client = Particle::Client.new
|
216
|
+
client.access_token = 'f1d52ea0de921fad300027763d8c5ebd03b1934d'
|
217
|
+
```
|
218
|
+
|
219
|
+
**All these following methods requires the account username (email) and password.**
|
220
|
+
|
221
|
+
List all tokens that can be used to access an account. Returns an `Array` of `Particle::Token`
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
Particle.tokens("me@example.com", "pa$$w0rd")
|
225
|
+
```
|
226
|
+
|
227
|
+
Log in and create a new token. Returns a `Particle::Token`. This will also set the token on the client for future calls.
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
Particle.login("me@example.com", "pa$$w0rd")
|
231
|
+
```
|
232
|
+
|
233
|
+
Create a token but don't set it on the client. Returns a `Particle::Token`
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
Particle.token.create("me@example.com", "pa$$w0rd")
|
237
|
+
```
|
238
|
+
|
239
|
+
Invalidate and delete a token. Returns true on success.
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
Particle.token('f1d52ea0de921fad300027763d8c5ebd03b1934d').remove("me@example.com", "pa$$w0rd")
|
243
|
+
Particle.tokens.first.remove("me@example.com", "pa$$w0rd")
|
244
|
+
```
|
245
|
+
|
246
|
+
See the [Particle Cloud API documentation about authentication and token][authentication docs] for more details.
|
247
|
+
|
248
|
+
[authentication docs]: http://docs.particle.io/core/api/#introduction-authentication
|
249
|
+
|
104
250
|
|
251
|
+
## Errors
|
105
252
|
|
106
|
-
|
253
|
+
When any API error occurs, a subclass of `Particle::Error` will be raised.
|
254
|
+
|
255
|
+
The actual error classes are
|
256
|
+
|
257
|
+
- `MissingTokenError`
|
258
|
+
- `BadRequest`
|
259
|
+
- `Unauthorized`
|
260
|
+
- `Forbidden`
|
261
|
+
- `NotFound`
|
262
|
+
- `TimedOut`
|
263
|
+
- `ServerError`
|
264
|
+
|
265
|
+
See [a description of each error on the Particle API docs][error docs].
|
266
|
+
|
267
|
+
[error docs]: http://docs.particle.io/core/api/#introduction-errors
|
268
|
+
|
269
|
+
## Advanced
|
270
|
+
|
271
|
+
All API endpoints are availble directly on the client object as method calls like `Particle.claim_device(id)` but the preferred usage is to call methods on domain objects like `Particle.device(id).claim`. See the various `Particle::Client` subclasses for more details.
|
107
272
|
|
108
273
|
### Accessing HTTP responses
|
109
274
|
|
@@ -117,6 +282,19 @@ response = Particle.last_response
|
|
117
282
|
headers = response.headers
|
118
283
|
```
|
119
284
|
|
285
|
+
## Support
|
286
|
+
|
287
|
+
Open a [GitHub issue][] if you find a bug.
|
288
|
+
|
289
|
+
[Join the conversion on the awesome Particle community forums][forum] to discuss any other topic!
|
290
|
+
|
291
|
+
[GitHub issue]: https://github.com/monkbroc/particlerb/issues
|
292
|
+
[forum]: http://community.particle.io/
|
293
|
+
|
294
|
+
## Versioning
|
295
|
+
|
296
|
+
particlerb follows the [Semantic Versioning](http://semver.org/) standard.
|
297
|
+
|
120
298
|
## Thanks
|
121
299
|
|
122
300
|
This gem is heavily inspired by [Octokit][] by GitHub. I stand on the shoulder of giants. Thanks!
|
@@ -1,13 +1,16 @@
|
|
1
|
+
require 'particle/device'
|
2
|
+
|
1
3
|
module Particle
|
2
4
|
class Client
|
3
5
|
|
4
|
-
#
|
6
|
+
# Client methods for the Particle device API
|
7
|
+
#
|
5
8
|
# @see http://docs.particle.io/core/api/#introduction-list-devices
|
6
9
|
module Devices
|
7
10
|
|
8
11
|
# Create a domain model for a Particle device
|
9
12
|
#
|
10
|
-
# @param target [String, Sawyer::Resource, Device] A device id, name or {Device} object
|
13
|
+
# @param target [String, Sawyer::Resource, Device] A device id, name, Sawyer::Resource or {Device} object
|
11
14
|
# @return [Device] A device object to interact with
|
12
15
|
def device(target)
|
13
16
|
if target.is_a? Device
|
@@ -15,18 +18,16 @@ module Particle
|
|
15
18
|
elsif target.respond_to?(:to_attrs)
|
16
19
|
Device.new(self, target.to_attrs)
|
17
20
|
else
|
18
|
-
Device.new(self, target
|
21
|
+
Device.new(self, target)
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
25
|
# List all Particle devices on the account
|
23
26
|
#
|
24
|
-
# @see http://docs.particle.io/core/api/#introduction-list-devices
|
25
|
-
#
|
26
27
|
# @return [Array<Device>] List of Particle devices to interact with
|
27
28
|
def devices
|
28
29
|
get(Device.list_path).map do |resource|
|
29
|
-
|
30
|
+
device(resource)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'particle/event'
|
2
|
+
|
3
|
+
module Particle
|
4
|
+
class Client
|
5
|
+
|
6
|
+
# Client methods for the Particle publish API
|
7
|
+
#
|
8
|
+
# @see http://docs.particle.io/core/api/#publishing-events
|
9
|
+
module Publish
|
10
|
+
|
11
|
+
# Publish an event to your devices
|
12
|
+
#
|
13
|
+
# @param options [Hash]
|
14
|
+
# * :name [String]: name of the event to publish
|
15
|
+
# * :data [String,Hash]: optional data to include with the event
|
16
|
+
# * :ttl [Number]: optional Time-To-Live in seconds for the
|
17
|
+
# event (currently ignored by the cloud)
|
18
|
+
# * :private [boolean]: optional key to indicate event should
|
19
|
+
# be published only to your own devices
|
20
|
+
# @return [boolean] true for success
|
21
|
+
def publish(options)
|
22
|
+
params = {
|
23
|
+
name: options.fetch(:name)
|
24
|
+
}
|
25
|
+
case options[:data]
|
26
|
+
when Hash, Array then params[:data] = options[:data].to_json
|
27
|
+
else params[:data] = options[:data].to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
params[:ttl] = options[:ttl] if options[:ttl]
|
31
|
+
params[:private] = true if options[:private]
|
32
|
+
|
33
|
+
result = post(Event.publish_path, params)
|
34
|
+
result.ok
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'particle/token'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Particle
|
5
|
+
class Client
|
6
|
+
|
7
|
+
# Client methods for the Particle authentication/token API
|
8
|
+
#
|
9
|
+
# @see http://docs.particle.io/core/api/#introduction-authentication
|
10
|
+
module Tokens
|
11
|
+
|
12
|
+
# Create a domain model for a Particle token
|
13
|
+
#
|
14
|
+
# @param target [String, Sawyer::Resource, Token] A token id, Sawyer::Resource or {Token} object
|
15
|
+
# @return [Token] A token object to interact with
|
16
|
+
def token(target = {})
|
17
|
+
if target.is_a? Token
|
18
|
+
target
|
19
|
+
elsif target.respond_to?(:to_attrs)
|
20
|
+
Token.new(self, target.to_attrs)
|
21
|
+
else
|
22
|
+
Token.new(self, target)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# List all Particle tokens for the account
|
27
|
+
#
|
28
|
+
# @param username [String] The username (email) used to log in to
|
29
|
+
# the Particle Cloud API
|
30
|
+
# @param password [String] The password used to log in to
|
31
|
+
# the Particle Cloud API
|
32
|
+
# @return [Array<Token>] List of Particle tokens
|
33
|
+
def tokens(username, password)
|
34
|
+
http_options = {
|
35
|
+
username: username,
|
36
|
+
password: password
|
37
|
+
}
|
38
|
+
request(:get, Token.list_path, "", http_options).map do |resource|
|
39
|
+
token(resource)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Authenticate with Particle and create a new token
|
44
|
+
#
|
45
|
+
# @param username [String] The username (email) used to log in to
|
46
|
+
# the Particle Cloud API
|
47
|
+
# @param password [String] The password used to log in to
|
48
|
+
# the Particle Cloud API
|
49
|
+
# @param options [Hash] Additional Particle Cloud API options to
|
50
|
+
# create the token.
|
51
|
+
# @return [Token] The token object
|
52
|
+
def create_token(username, password, options = {})
|
53
|
+
data = URI.encode_www_form({
|
54
|
+
grant_type: 'password', # specified by docs
|
55
|
+
username: username,
|
56
|
+
password: password
|
57
|
+
}.merge(options))
|
58
|
+
http_options = {
|
59
|
+
headers: { content_type: "application/x-www-form-urlencoded" },
|
60
|
+
username: 'particle', # specified by docs
|
61
|
+
password: 'particle' # specified by docs
|
62
|
+
}
|
63
|
+
result = request(:post, Token.create_path, data, http_options)
|
64
|
+
token(result.access_token)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Authenticate with Particle and start using the token on the
|
68
|
+
# client right away
|
69
|
+
#
|
70
|
+
# @param username [String] The username (email) used to log in to
|
71
|
+
# the Particle Cloud API
|
72
|
+
# @param password [String] The password used to log in to
|
73
|
+
# the Particle Cloud API
|
74
|
+
# @param options [Hash] Additional Particle Cloud API options to
|
75
|
+
# create the token.
|
76
|
+
# @return [Token] The token object
|
77
|
+
def login(username, password, options = {})
|
78
|
+
token = create_token(username, password, options)
|
79
|
+
self.access_token = token
|
80
|
+
token
|
81
|
+
end
|
82
|
+
|
83
|
+
# Remove a Particle token
|
84
|
+
#
|
85
|
+
# @param username [String] The username (email) used to log in to
|
86
|
+
# the Particle Cloud API
|
87
|
+
# @param password [String] The password used to log in to
|
88
|
+
# the Particle Cloud API
|
89
|
+
# @param target [String, Token] An token id or {Token} object
|
90
|
+
# @return [boolean] true for success
|
91
|
+
def remove_token(username, password, target)
|
92
|
+
http_options = {
|
93
|
+
username: username,
|
94
|
+
password: password
|
95
|
+
}
|
96
|
+
result = request(:delete, token(target).path, "", http_options)
|
97
|
+
result.ok
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'particle/webhook'
|
2
|
+
|
3
|
+
module Particle
|
4
|
+
class Client
|
5
|
+
|
6
|
+
# Client methods for the Particle webhook API
|
7
|
+
#
|
8
|
+
# @see http://docs.particle.io/core/webhooks/
|
9
|
+
module Webhooks
|
10
|
+
|
11
|
+
# Create a domain model for a Particle webhook
|
12
|
+
#
|
13
|
+
# @param target [String, Sawyer::Resource, Webhook] A webhook id, Sawyer::Resource or {Device} object
|
14
|
+
# @return [Webhook] A webhook object to interact with
|
15
|
+
def webhook(target)
|
16
|
+
if target.is_a? Webhook
|
17
|
+
target
|
18
|
+
elsif target.respond_to?(:to_attrs)
|
19
|
+
Webhook.new(self, target.to_attrs)
|
20
|
+
else
|
21
|
+
Webhook.new(self, target)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all Particle webhooks on the account
|
26
|
+
#
|
27
|
+
# @return [Array<Webhook>] List of Particle webhooks to interact with
|
28
|
+
def webhooks
|
29
|
+
get(Webhook.list_path).map do |resource|
|
30
|
+
webhook(resource)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get information about a Particle webhook
|
35
|
+
#
|
36
|
+
# The Particle cloud will send a test message to the webhook URL
|
37
|
+
# when this is called
|
38
|
+
#
|
39
|
+
# @param target [String, Webhook] A webhook id or {Webhook} object
|
40
|
+
# @return [Hash] The webhook attributes and test message response
|
41
|
+
def webhook_attributes(target)
|
42
|
+
result = get(webhook(target).path)
|
43
|
+
result.to_attrs
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a new Particle webhook
|
47
|
+
#
|
48
|
+
# @param options [Hash] Options to configure the webhook
|
49
|
+
# @return [Webhook] The webhook object
|
50
|
+
# @see http://docs.particle.io/core/webhooks/#Webhook-options
|
51
|
+
def create_webhook(options)
|
52
|
+
result = post(Webhook.create_path, options)
|
53
|
+
webhook(result)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Remove a Particle webhook
|
57
|
+
#
|
58
|
+
# @param target [String, Webhook] A webhook id or {Webhook} object
|
59
|
+
# @return [boolean] true for success
|
60
|
+
def remove_webhook(target)
|
61
|
+
result = delete(webhook(target).path)
|
62
|
+
result.ok
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/particle/client.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'particle/connection'
|
2
|
-
require 'particle/device'
|
3
2
|
require 'particle/client/devices'
|
3
|
+
require 'particle/client/publish'
|
4
|
+
require 'particle/client/webhooks'
|
5
|
+
require 'particle/client/tokens'
|
4
6
|
|
5
7
|
module Particle
|
6
8
|
|
@@ -11,6 +13,9 @@ module Particle
|
|
11
13
|
include Particle::Configurable
|
12
14
|
include Particle::Connection
|
13
15
|
include Particle::Client::Devices
|
16
|
+
include Particle::Client::Publish
|
17
|
+
include Particle::Client::Webhooks
|
18
|
+
include Particle::Client::Tokens
|
14
19
|
|
15
20
|
def initialize(options = {})
|
16
21
|
# Use options passed in, but fall back to module defaults
|
@@ -38,7 +43,12 @@ module Particle
|
|
38
43
|
# @param value [String] 40 character Particle OAuth2 access token
|
39
44
|
def access_token=(value)
|
40
45
|
reset_agent
|
41
|
-
@access_token =
|
46
|
+
@access_token =
|
47
|
+
if value.respond_to? :access_token
|
48
|
+
value.access_token
|
49
|
+
else
|
50
|
+
value
|
51
|
+
end
|
42
52
|
end
|
43
53
|
end
|
44
54
|
end
|
@@ -3,7 +3,7 @@ module Particle
|
|
3
3
|
# Configuration options for {Client}, defaulting to values in
|
4
4
|
# {Default}
|
5
5
|
module Configurable
|
6
|
-
# @!attribute [
|
6
|
+
# @!attribute [String] access_token
|
7
7
|
# @see http://docs.particle.io/core/api/#introduction-authentication
|
8
8
|
# @return [String] Particle access token for authentication
|
9
9
|
# @!attribute api_endpoint
|
data/lib/particle/connection.rb
CHANGED
@@ -57,15 +57,6 @@ module Particle
|
|
57
57
|
request :delete, url, options
|
58
58
|
end
|
59
59
|
|
60
|
-
# Make a HTTP HEAD request
|
61
|
-
#
|
62
|
-
# @param url [String] The path, relative to {#api_endpoint}
|
63
|
-
# @param options [Hash] Query and header params for request
|
64
|
-
# @return [Sawyer::Resource]
|
65
|
-
def head(url, options = {})
|
66
|
-
request :head, url, parse_query_and_convenience_headers(options)
|
67
|
-
end
|
68
|
-
|
69
60
|
# Hypermedia agent for the Particle API
|
70
61
|
#
|
71
62
|
# @return [Sawyer::Agent]
|
@@ -98,13 +89,21 @@ module Particle
|
|
98
89
|
|
99
90
|
def request(method, path, data, options = {})
|
100
91
|
if data.is_a?(Hash)
|
101
|
-
options[:query]
|
102
|
-
options[:headers]
|
92
|
+
options[:query] ||= data.delete(:query) || {}
|
93
|
+
options[:headers] ||= data.delete(:headers) || {}
|
103
94
|
if accept = data.delete(:accept)
|
104
95
|
options[:headers][:accept] = accept
|
105
96
|
end
|
106
97
|
end
|
107
98
|
|
99
|
+
username = options.delete(:username)
|
100
|
+
password = options.delete(:password)
|
101
|
+
if username && password
|
102
|
+
options[:headers] ||= {}
|
103
|
+
options[:headers][:authorization] =
|
104
|
+
basic_auth_header(username, password)
|
105
|
+
end
|
106
|
+
|
108
107
|
@last_response = response = agent.call(method, URI::Parser.new.escape(path.to_s), data, options)
|
109
108
|
response.data
|
110
109
|
end
|
@@ -120,6 +119,12 @@ module Particle
|
|
120
119
|
|
121
120
|
opts
|
122
121
|
end
|
122
|
+
|
123
|
+
# Temporarily set the Authorization to use basic auth
|
124
|
+
def basic_auth_header(username, password)
|
125
|
+
Faraday::Request.lookup_middleware(:basic_auth).
|
126
|
+
header(username, password)
|
127
|
+
end
|
123
128
|
end
|
124
129
|
end
|
125
130
|
|
data/lib/particle/device.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
+
require 'particle/model'
|
2
|
+
|
1
3
|
module Particle
|
2
4
|
|
3
5
|
# Domain model for one Particle device
|
4
|
-
class Device
|
6
|
+
class Device < Model
|
5
7
|
ID_REGEX = /\h{24}/
|
6
8
|
|
7
9
|
def initialize(client, attributes)
|
8
|
-
|
9
|
-
|
10
|
+
super(client, attributes)
|
11
|
+
|
10
12
|
if attributes.is_a? String
|
11
13
|
if attributes =~ ID_REGEX
|
12
|
-
{ id: attributes }
|
14
|
+
@attributes = { id: attributes }
|
13
15
|
else
|
14
|
-
{ name: attributes }
|
16
|
+
@attributes = { name: attributes }
|
15
17
|
end
|
16
|
-
else
|
17
|
-
attributes
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -32,17 +32,10 @@ module Particle
|
|
32
32
|
@attributes[:id] || @attributes[:name]
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
attributes[key.to_sym]
|
38
|
-
end
|
39
|
-
end
|
40
|
-
alias_method :connected?, :connected
|
35
|
+
attribute_reader :connected, :functions, :variables, :product_id,
|
36
|
+
:last_heard, :last_app, :last_ip_address
|
41
37
|
|
42
|
-
|
43
|
-
get_attributes unless @loaded
|
44
|
-
@attributes
|
45
|
-
end
|
38
|
+
alias_method :connected?, :connected
|
46
39
|
|
47
40
|
def get_attributes
|
48
41
|
@loaded = true
|
data/lib/particle/error.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Particle
|
2
|
+
|
3
|
+
# Base class for domain models
|
4
|
+
class Model
|
5
|
+
def initialize(client, attributes)
|
6
|
+
@client = client
|
7
|
+
@attributes =
|
8
|
+
if attributes.is_a? String
|
9
|
+
{ id: attributes }
|
10
|
+
else
|
11
|
+
# Consider attributes loaded when passed in through constructor
|
12
|
+
@loaded = true
|
13
|
+
attributes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Display the attributes when inspecting the object in the console
|
18
|
+
def inspect
|
19
|
+
"#<#{self.class} #{@attributes}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Accessor for the id
|
23
|
+
def id
|
24
|
+
@attributes[:id]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Define accessor methods for attributes.
|
28
|
+
#
|
29
|
+
# Will load the attributes from the cloud if not already done
|
30
|
+
def self.attribute_reader(*keys)
|
31
|
+
keys.each do |key|
|
32
|
+
define_method key do
|
33
|
+
attributes[key]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Hash of all attributes returned by the cloud
|
39
|
+
def attributes
|
40
|
+
get_attributes unless @loaded
|
41
|
+
@attributes
|
42
|
+
end
|
43
|
+
|
44
|
+
# Load the model attributes with the correct API call in subclasses
|
45
|
+
def get_attributes
|
46
|
+
raise "Implement in subclasses and set @loaded = true"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Particle
|
2
|
+
|
3
|
+
# Domain model for one Particle token
|
4
|
+
class Token < Model
|
5
|
+
def initialize(client, attributes)
|
6
|
+
@client = client
|
7
|
+
@attributes =
|
8
|
+
if attributes.is_a? String
|
9
|
+
{ token: attributes }
|
10
|
+
else
|
11
|
+
# Consider attributes loaded when passed in through constructor
|
12
|
+
@loaded = true
|
13
|
+
attributes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# The id of the token
|
18
|
+
def token
|
19
|
+
@attributes[:token]
|
20
|
+
end
|
21
|
+
alias_method :id, :token
|
22
|
+
alias_method :access_token, :token
|
23
|
+
|
24
|
+
attribute_reader :expires_at, :client
|
25
|
+
|
26
|
+
# Tokens can't be loaded. What you see is what you get...
|
27
|
+
def get_attributes
|
28
|
+
@loaded = true
|
29
|
+
@attributes
|
30
|
+
end
|
31
|
+
|
32
|
+
# Text representation of the token, masking the secret part
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
def inspect
|
36
|
+
inspected = super
|
37
|
+
|
38
|
+
# Only show last 4 of token, secret
|
39
|
+
if id
|
40
|
+
inspected = inspected.gsub! id, "#{'*'*36}#{id[36..-1]}"
|
41
|
+
end
|
42
|
+
|
43
|
+
inspected
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create a Particle token
|
47
|
+
# @param username [String] The username (email) used to log in to
|
48
|
+
# the Particle Cloud API
|
49
|
+
# @param password [String] The password used to log in to
|
50
|
+
# the Particle Cloud API
|
51
|
+
def create(username, password)
|
52
|
+
@client.create_token(username, password)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Remove a Particle token
|
56
|
+
# @param username [String] The username (email) used to log in to
|
57
|
+
# the Particle Cloud API
|
58
|
+
# @param password [String] The password used to log in to
|
59
|
+
# the Particle Cloud API
|
60
|
+
def remove(username, password)
|
61
|
+
@client.remove_token(username, password, self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.list_path
|
65
|
+
"v1/access_tokens"
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.create_path
|
69
|
+
"/oauth/token"
|
70
|
+
end
|
71
|
+
|
72
|
+
def path
|
73
|
+
"/v1/access_tokens/#{access_token}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/particle/version.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'particle/model'
|
2
|
+
|
3
|
+
module Particle
|
4
|
+
|
5
|
+
# Domain model for one Particle device
|
6
|
+
class Webhook < Model
|
7
|
+
def initialize(client, attributes)
|
8
|
+
super(client, attributes)
|
9
|
+
end
|
10
|
+
|
11
|
+
attribute_reader :url, :deviceID, :event, :created_at, :mydevices,
|
12
|
+
:requestType, :headers, :json, :query, :auth
|
13
|
+
|
14
|
+
# The response of the web server to a test message
|
15
|
+
# If nil, check error
|
16
|
+
def response
|
17
|
+
get_attributes unless @loaded
|
18
|
+
@response
|
19
|
+
end
|
20
|
+
|
21
|
+
# The error from the web server to a test message
|
22
|
+
# If nil, check response
|
23
|
+
def error
|
24
|
+
get_attributes unless @loaded
|
25
|
+
@error
|
26
|
+
end
|
27
|
+
|
28
|
+
# Force reloading the attributes for the webhook
|
29
|
+
def get_attributes
|
30
|
+
@loaded = true
|
31
|
+
result = @client.webhook_attributes(self)
|
32
|
+
@response = result[:response]
|
33
|
+
@error = result[:error]
|
34
|
+
@attributes = result[:webhook]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a Particle webhook
|
38
|
+
def create
|
39
|
+
new_webhook = @client.create_webhook(@attributes)
|
40
|
+
@attributes = new_webhook.attributes
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remove a Particle webhook
|
45
|
+
def remove
|
46
|
+
@client.remove_webhook(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.list_path
|
50
|
+
"v1/webhooks"
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.create_path
|
54
|
+
"v1/webhooks"
|
55
|
+
end
|
56
|
+
|
57
|
+
def path
|
58
|
+
"/v1/webhooks/#{id}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
data/lib/particle.rb
CHANGED
@@ -11,6 +11,25 @@ module Particle
|
|
11
11
|
@client = Particle::Client.new(options)
|
12
12
|
end
|
13
13
|
|
14
|
+
# Authenticate with Particle and start using the new token as the
|
15
|
+
# global token
|
16
|
+
#
|
17
|
+
# Delegates actual functionality to the client.
|
18
|
+
#
|
19
|
+
# @param username [String] The username (email) used to log in to
|
20
|
+
# the Particle Cloud API
|
21
|
+
# @param password [String] The password used to log in to
|
22
|
+
# the Particle Cloud API
|
23
|
+
# @param options [Hash] Additional Particle Cloud API options to
|
24
|
+
# create the token.
|
25
|
+
# @return [Token] The token object
|
26
|
+
# @see {Particle::Client::Tokens#login}
|
27
|
+
def login(username, password, options = {})
|
28
|
+
token = client.login(username, password, options)
|
29
|
+
self.access_token = token.access_token
|
30
|
+
token
|
31
|
+
end
|
32
|
+
|
14
33
|
private
|
15
34
|
|
16
35
|
def respond_to_missing?(method_name, include_private = false)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: particlerb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julien Vanier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sawyer
|
@@ -149,13 +149,20 @@ files:
|
|
149
149
|
- lib/particle.rb
|
150
150
|
- lib/particle/client.rb
|
151
151
|
- lib/particle/client/devices.rb
|
152
|
+
- lib/particle/client/publish.rb
|
153
|
+
- lib/particle/client/tokens.rb
|
154
|
+
- lib/particle/client/webhooks.rb
|
152
155
|
- lib/particle/configurable.rb
|
153
156
|
- lib/particle/connection.rb
|
154
157
|
- lib/particle/default.rb
|
155
158
|
- lib/particle/device.rb
|
156
159
|
- lib/particle/error.rb
|
160
|
+
- lib/particle/event.rb
|
161
|
+
- lib/particle/model.rb
|
157
162
|
- lib/particle/response/raise_error.rb
|
163
|
+
- lib/particle/token.rb
|
158
164
|
- lib/particle/version.rb
|
165
|
+
- lib/particle/webhook.rb
|
159
166
|
- lib/particlerb.rb
|
160
167
|
- particlerb.gemspec
|
161
168
|
homepage: https://github.com/monkbroc/particlerb
|