tempoiq 1.0.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/Gemfile +6 -0
- data/README.md +35 -0
- data/Rakefile +28 -0
- data/lib/tempoiq.rb +7 -0
- data/lib/tempoiq/client.rb +450 -0
- data/lib/tempoiq/constants.rb +6 -0
- data/lib/tempoiq/models/bulk_write.rb +31 -0
- data/lib/tempoiq/models/cursor.rb +48 -0
- data/lib/tempoiq/models/datapoint.rb +28 -0
- data/lib/tempoiq/models/delete_summary.rb +12 -0
- data/lib/tempoiq/models/device.rb +40 -0
- data/lib/tempoiq/models/find.rb +18 -0
- data/lib/tempoiq/models/multi_status.rb +27 -0
- data/lib/tempoiq/models/pipeline.rb +86 -0
- data/lib/tempoiq/models/query.rb +20 -0
- data/lib/tempoiq/models/read.rb +21 -0
- data/lib/tempoiq/models/row.rb +32 -0
- data/lib/tempoiq/models/search.rb +17 -0
- data/lib/tempoiq/models/selection.rb +57 -0
- data/lib/tempoiq/models/sensor.rb +28 -0
- data/lib/tempoiq/models/single.rb +17 -0
- data/lib/tempoiq/remoter/http_result.rb +42 -0
- data/lib/tempoiq/remoter/live_remoter.rb +70 -0
- data/lib/tempoiq/remoter/stubbed_remoter.rb +54 -0
- data/lib/trusted-certs.crt +360 -0
- data/test/client_test.rb +629 -0
- data/test/integration/integration-credentials.yml +5 -0
- data/test/integration/test_live_client.rb +19 -0
- data/test/unit/test_cursor.rb +116 -0
- data/test/unit/test_stubbed_client.rb +15 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 46035694511c1eab2ca20938cfc129a20e84e0b2
|
4
|
+
data.tar.gz: 4b6a6595df82676875deaefbc406bed40bad8278
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 453cba05af4add9361f01c4449478a3436e8ca4f988441ae305d14128c5858eb2df6936531c1aa8ac2d048c3a0af9f2d5f539b6fc86b8e081cdee443db646226
|
7
|
+
data.tar.gz: 8423b80cf5a336f99757435a4f22745dfbb3d1a767a087281510fbbe4d5333a7fc9c9647bbb8f97f62aeb1354e7d91d8a81ca7eee60f49d720ec9b8724c6367a
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# TempoIQ HTTP Ruby Client
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
```
|
6
|
+
gem build tempoiq.gemspec
|
7
|
+
gem install tempoiq-<version>.gem
|
8
|
+
```
|
9
|
+
|
10
|
+
## Quickstart
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require 'tempoiq'
|
14
|
+
|
15
|
+
client = TempoIQ::Client.new('key', 'secret', 'myco.backend.tempoiq.com')
|
16
|
+
client.create_device('device1')
|
17
|
+
client.list_devices.to_a
|
18
|
+
```
|
19
|
+
|
20
|
+
For more example usage, see the Client ruby docs.
|
21
|
+
|
22
|
+
## Test Suite
|
23
|
+
|
24
|
+
To run the test suite against local stubs:
|
25
|
+
|
26
|
+
```
|
27
|
+
rake
|
28
|
+
```
|
29
|
+
|
30
|
+
If you'd like to run the test suite against an actual live backend,
|
31
|
+
edit `test/integration/integration-credentials.yml`, and run:
|
32
|
+
|
33
|
+
```
|
34
|
+
rake test:integration
|
35
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
gem 'rdoc', '>= 2.4.2'
|
2
|
+
require 'rdoc/task'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
task :default => "test:unit"
|
6
|
+
|
7
|
+
Rake::TestTask.new do |task|
|
8
|
+
task.name = "test:unit"
|
9
|
+
task.libs << "tests"
|
10
|
+
task.test_files = FileList["test/unit/test*.rb"]
|
11
|
+
task.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::TestTask.new do |task|
|
15
|
+
task.name = "test:integration"
|
16
|
+
task.libs << "tests"
|
17
|
+
task.test_files = FileList["test/integration/test*.rb"]
|
18
|
+
task.verbose = true
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Generate API documentation'
|
22
|
+
RDoc::Task.new do |rd|
|
23
|
+
rd.rdoc_files.include("README.md", "lib/**/*.rb")
|
24
|
+
rd.options << '--inline-source'
|
25
|
+
rd.options << '--line-numbers'
|
26
|
+
rd.options << '--main=README.md'
|
27
|
+
end
|
28
|
+
|
data/lib/tempoiq.rb
ADDED
@@ -0,0 +1,450 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require 'tempoiq/models/bulk_write'
|
6
|
+
require 'tempoiq/models/cursor'
|
7
|
+
require 'tempoiq/models/datapoint'
|
8
|
+
require 'tempoiq/models/delete_summary'
|
9
|
+
require 'tempoiq/models/device'
|
10
|
+
require 'tempoiq/models/find'
|
11
|
+
require 'tempoiq/models/multi_status'
|
12
|
+
require 'tempoiq/models/pipeline'
|
13
|
+
require 'tempoiq/models/query'
|
14
|
+
require 'tempoiq/models/read'
|
15
|
+
require 'tempoiq/models/row'
|
16
|
+
require 'tempoiq/models/search'
|
17
|
+
require 'tempoiq/models/selection'
|
18
|
+
require 'tempoiq/models/single'
|
19
|
+
require 'tempoiq/remoter/live_remoter'
|
20
|
+
|
21
|
+
module TempoIQ
|
22
|
+
class ClientError < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
MEDIA_PREFIX = "application/prs.tempoiq"
|
26
|
+
|
27
|
+
# TempoIQ::Client is the main interface to your TempoIQ backend.
|
28
|
+
#
|
29
|
+
# The client is broken down into two main sections:
|
30
|
+
#
|
31
|
+
# [Device Provisioning]
|
32
|
+
# - #create_device
|
33
|
+
# - #update_device
|
34
|
+
# - #delete_device
|
35
|
+
# - #delete_devices
|
36
|
+
# - #get_device
|
37
|
+
# - #list_devices
|
38
|
+
#
|
39
|
+
# [DataPoint Reading / Writing]
|
40
|
+
# - #write_bulk
|
41
|
+
# - #write_device
|
42
|
+
# - #read
|
43
|
+
#
|
44
|
+
# == Key Concepts:
|
45
|
+
#
|
46
|
+
# === Selection - A way to describe a grouping of related objects. Used primarily in Device / Sensor queries.
|
47
|
+
class Client
|
48
|
+
# Your TempoIQ backend key (String)
|
49
|
+
attr_reader :key
|
50
|
+
|
51
|
+
# TempoIQ backend secret (String)
|
52
|
+
attr_reader :secret
|
53
|
+
|
54
|
+
# TempoIQ backend host, found on your TempoIQ backend dashboard (String)
|
55
|
+
attr_reader :host
|
56
|
+
|
57
|
+
# Whether to use SSL or not. Defaults to true (Boolean, default: true)
|
58
|
+
attr_reader :secure
|
59
|
+
|
60
|
+
# Makes the backend calls (Remoter, default: LiveRemoter)
|
61
|
+
attr_reader :remoter
|
62
|
+
|
63
|
+
# Create a TempoIQ API Client
|
64
|
+
#
|
65
|
+
# * +key+ [String] - Your TempoIQ backend key
|
66
|
+
# * +secret+ [String] - TempoIQ backend secret
|
67
|
+
# * +host+ [String] - TempoIQ backend host, found on your TempoIQ backend dashboard
|
68
|
+
# * +port+ (optional) [Integer] - TempoIQ backend port
|
69
|
+
# * +opts+ (optional) [Hash] - Optional client parameters
|
70
|
+
#
|
71
|
+
# ==== Options
|
72
|
+
# * +:secure+ [Boolean] - Whether to use SSL or not. Defaults to true
|
73
|
+
# * +:remoter+ [Remoter] - Which backend to issue calls with. Defaults to LiveRemoter
|
74
|
+
def initialize(key, secret, host, port = 443, opts = {})
|
75
|
+
@key = key
|
76
|
+
@secret = secret
|
77
|
+
@host = host
|
78
|
+
@port = port
|
79
|
+
@secure = opts.has_key?(:secure) ? opts[:secure] : true
|
80
|
+
@remoter = opts[:remoter] || LiveRemoter.new(key, secret, host, port, secure)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Create a Device in your TempoIQ backend
|
84
|
+
#
|
85
|
+
# * +key+ [String] - Device key
|
86
|
+
# * +name+ (optional) [String] - Human readable device name
|
87
|
+
# * +attributes+ (optional) [Hash] - A hash of device attributes. Keys / values are strings.
|
88
|
+
# * +sensors+ (optional) [Array] - An array of Sensor objects to attach to the device
|
89
|
+
#
|
90
|
+
# On success:
|
91
|
+
# - Returns the Device created
|
92
|
+
# On failure:
|
93
|
+
# - Raises HttpException
|
94
|
+
#
|
95
|
+
# ==== Example
|
96
|
+
#
|
97
|
+
# # Create a device keyed 'heatpump4789' with 2 attached sensors
|
98
|
+
# device = client.create_device('heatpump4789', 'Basement Heat Pump',
|
99
|
+
# 'building' => '445 W Erie', 'model' => '75ZX',
|
100
|
+
# TempoIQ::Sensor.new('temp-1'), TempoIQ::Sensor.new('pressure-1'))
|
101
|
+
def create_device(key, name = "", attributes = {}, *sensors)
|
102
|
+
device = Device.new(key, name, attributes, *sensors)
|
103
|
+
remoter.post("/v2/devices", JSON.dump(device.to_hash)).on_success do |result|
|
104
|
+
json = JSON.parse(result.body)
|
105
|
+
Device.from_hash(json)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Fetch a device by key
|
110
|
+
#
|
111
|
+
# * +device_key+ [String] - The device key to fetch by
|
112
|
+
#
|
113
|
+
# On success:
|
114
|
+
# - Returns the Device found, nil when not found
|
115
|
+
# On failure:
|
116
|
+
# - Raises HttpException
|
117
|
+
#
|
118
|
+
# ==== Example
|
119
|
+
# # Lookup the device keyed 'heatpump4789'
|
120
|
+
# device = client.get_device('heatpump4789')
|
121
|
+
# device.sensors.each { |sensor| puts sensor.key }
|
122
|
+
def get_device(device_key)
|
123
|
+
result = remoter.get("/v2/devices/#{URI.escape(device_key)}")
|
124
|
+
case result.code
|
125
|
+
when HttpResult::OK
|
126
|
+
json = JSON.parse(result.body)
|
127
|
+
Device.from_hash(json)
|
128
|
+
when HttpResult::NOT_FOUND
|
129
|
+
nil
|
130
|
+
else
|
131
|
+
raise HttpException.new(result)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Search for a set of devices based on Selection criteria
|
136
|
+
#
|
137
|
+
# * +selection+ - Device search criteria. See Selection.
|
138
|
+
#
|
139
|
+
# On success:
|
140
|
+
# - Return Cursor of Devices.
|
141
|
+
# On failure:
|
142
|
+
# - Raises HttpException after first Cursor iteration (lazy iteration)
|
143
|
+
#
|
144
|
+
# ==== Example
|
145
|
+
# # Select devices in building in the Evanston region
|
146
|
+
# client.list_devices(:devices => {:and => [{:attribute_key => 'building'}, {:attributes => {'region' => 'Evanston'}}]})
|
147
|
+
def list_devices(selection = {:devices => "all"}, opts = {})
|
148
|
+
query = Query.new(Search.new("devices", selection),
|
149
|
+
Find.new(opts[:limit]),
|
150
|
+
nil)
|
151
|
+
Cursor.new(Device, remoter, "/v2/devices", query, media_types(:accept => [media_type("error", "v1"), media_type("device-collection", "v2")],
|
152
|
+
:content => media_type("query", "v1")))
|
153
|
+
end
|
154
|
+
|
155
|
+
# Delete a device by key
|
156
|
+
#
|
157
|
+
# * +device_key+ [String] - The device key to delete by
|
158
|
+
#
|
159
|
+
# On succces:
|
160
|
+
# - Return true if Device found, false if Device not found
|
161
|
+
# On failure:
|
162
|
+
# - Raises HttpException
|
163
|
+
#
|
164
|
+
# ==== Example
|
165
|
+
# # Delete device keyed 'heatpump4576'
|
166
|
+
# deleted = client.delete_device('heatpump4576')
|
167
|
+
# if deleted
|
168
|
+
# puts "Device was deleted"
|
169
|
+
# end
|
170
|
+
def delete_device(device_key)
|
171
|
+
result = remoter.delete("/v2/devices/#{URI.escape(device_key)}")
|
172
|
+
case result.code
|
173
|
+
when HttpResult::OK
|
174
|
+
true
|
175
|
+
when HttpResult::NOT_FOUND
|
176
|
+
false
|
177
|
+
else
|
178
|
+
raise HttpException.new(result)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Delete a set of devices by Selection criteria
|
183
|
+
#
|
184
|
+
# * +selection+ - Device search criteria. See Selection.
|
185
|
+
#
|
186
|
+
# On success:
|
187
|
+
# - Return a DeleteSummary object
|
188
|
+
# On failure:
|
189
|
+
# - Raises HttpException
|
190
|
+
#
|
191
|
+
# ==== Example
|
192
|
+
# # Delete all devices in building 'b4346'
|
193
|
+
# summary = client.delete_devices(:devices => {:attributes => {'building' => 'b4346'}})
|
194
|
+
# puts "Number of devices deleted: #{summary.deleted}"
|
195
|
+
def delete_devices(selection)
|
196
|
+
query = Query.new(Search.new("devices", selection),
|
197
|
+
Find.new,
|
198
|
+
nil)
|
199
|
+
|
200
|
+
remoter.delete("/v2/devices", JSON.dump(query.to_hash)).on_success do |result|
|
201
|
+
json = JSON.parse(result.body)
|
202
|
+
DeleteSummary.new(json['deleted'])
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Update a device
|
207
|
+
#
|
208
|
+
# * +device+ - Updated Device object.
|
209
|
+
#
|
210
|
+
# On success:
|
211
|
+
# - Return updated Device on found, nil on Device not found
|
212
|
+
# On failure:
|
213
|
+
# - Raises HttpException
|
214
|
+
#
|
215
|
+
# ==== Example
|
216
|
+
#
|
217
|
+
# # Get a device and update it's name
|
218
|
+
# device = client.get_device('building1234')
|
219
|
+
# if device
|
220
|
+
# device.name = "Updated name"
|
221
|
+
# client.update_device(device)
|
222
|
+
# end
|
223
|
+
def update_device(device)
|
224
|
+
remoter.put("/v2/devices/#{URI.escape(device.key)}", JSON.dump(device.to_hash)).on_success do |result|
|
225
|
+
json = JSON.parse(result.body)
|
226
|
+
Device.from_hash(json)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Write multiple datapoints to multiple device sensors. This function
|
231
|
+
# is generally useful for importing data to many devices at once.
|
232
|
+
#
|
233
|
+
# * +bulk_write+ - The write request to send to the backend. Yielded to the block.
|
234
|
+
#
|
235
|
+
# On success:
|
236
|
+
# - Returns MultiStatus
|
237
|
+
# On partial success:
|
238
|
+
# - Returns MultiStatus
|
239
|
+
# On failure:
|
240
|
+
# - Raises HttpException
|
241
|
+
#
|
242
|
+
# ==== Example
|
243
|
+
# # Write to 'device1' and 'device2' with different sensor readings
|
244
|
+
# status = client.write_bulk do |write|
|
245
|
+
# ts = Time.now
|
246
|
+
# write.add('device1', 'temp1', TempoIQ::DataPoint.new(ts, 1.23))
|
247
|
+
# write.add('device2', 'temp1', TempoIQ::DataPoint.new(ts, 2.34))
|
248
|
+
# end
|
249
|
+
#
|
250
|
+
# if status.succes?
|
251
|
+
# puts "All datapoints written successfully"
|
252
|
+
# elsif status.partial_success?
|
253
|
+
# status.failures.each do |device_key, message|
|
254
|
+
# puts "Failed to write #{device_key}, message: #{message}"
|
255
|
+
# end
|
256
|
+
# end
|
257
|
+
def write_bulk(bulk_write = nil, &block)
|
258
|
+
bulk = bulk_write || BulkWrite.new
|
259
|
+
if block_given?
|
260
|
+
yield bulk
|
261
|
+
elsif bulk_write.nil?
|
262
|
+
raise ClientError.new("You must pass either a bulk write object, or provide a block")
|
263
|
+
end
|
264
|
+
|
265
|
+
result = remoter.post("/v2/write", JSON.dump(bulk.to_hash))
|
266
|
+
if result.code == HttpResult::OK
|
267
|
+
MultiStatus.new
|
268
|
+
elsif result.code == HttpResult::MULTI
|
269
|
+
json = JSON.parse(result.body)
|
270
|
+
MultiStatus.new(json)
|
271
|
+
else
|
272
|
+
raise HttpException.new(result)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Write to multiple sensors in a single device, at the same timestamp. Useful for
|
277
|
+
# 'sampling' from all the sensors on a device and ensuring that the timestamps align.
|
278
|
+
#
|
279
|
+
# * +device_key+ [String] - Device key to write to
|
280
|
+
# * +ts+ [Time] - Timestamp that datapoints will be written at
|
281
|
+
# * +values+ [Hash] - Hash from sensor_key => value
|
282
|
+
#
|
283
|
+
# On success:
|
284
|
+
# - Return true
|
285
|
+
# On failure:
|
286
|
+
# - Raises HttpException
|
287
|
+
#
|
288
|
+
# ==== Example
|
289
|
+
#
|
290
|
+
# ts = Time.now
|
291
|
+
# status = client.write_device('device1', ts, 'temp1' => 4.0, 'temp2' => 4.2)
|
292
|
+
# if status.succes?
|
293
|
+
# puts "All datapoints written successfully"
|
294
|
+
# end
|
295
|
+
def write_device(device_key, ts, values)
|
296
|
+
bulk = BulkWrite.new
|
297
|
+
values.each do |sensor_key, value|
|
298
|
+
bulk.add(device_key, sensor_key, DataPoint.new(ts, value))
|
299
|
+
end
|
300
|
+
write_bulk(bulk).success?
|
301
|
+
end
|
302
|
+
|
303
|
+
# Read from a set of Devices / Sensors, with an optional functional pipeline
|
304
|
+
# to transform the values.
|
305
|
+
#
|
306
|
+
# * +selection+ [Selection] - Device selection, describes which Devices / Sensors we should operate on
|
307
|
+
# * +start+ [Time] - Read start interval
|
308
|
+
# * +stop+ [Time] - Read stop interval
|
309
|
+
# * +pipeline+ [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.
|
310
|
+
#
|
311
|
+
# On success:
|
312
|
+
# - Return a Cursor of Row objects
|
313
|
+
# On failure:
|
314
|
+
# - Raise an HttpException
|
315
|
+
#
|
316
|
+
# ==== Examples
|
317
|
+
# # Read raw datapoints from Device 'bulding4567' Sensor 'temp1'
|
318
|
+
# start = Time.utc(2014, 1, 1)
|
319
|
+
# stop = Time.utc(2014, 1, 2)
|
320
|
+
# rows = client.read({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}}, start, stop)
|
321
|
+
# rows.each do |row|
|
322
|
+
# puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
|
323
|
+
# end
|
324
|
+
#
|
325
|
+
# # Find the daily mean temperature in Device 'building4567' across sensors 'temp1' and 'temp2'
|
326
|
+
# start = Time.utc(2014, 1, 1)
|
327
|
+
# stop = Time.utc(2014, 2, 2)
|
328
|
+
# rows = client.read({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}}, start, stop) do |pipeline|
|
329
|
+
# pipeline.rollup("1day", :mean, start)
|
330
|
+
# pipeline.aggregate(:mean)
|
331
|
+
# end
|
332
|
+
#
|
333
|
+
# rows.each do |row|
|
334
|
+
# puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
|
335
|
+
# end
|
336
|
+
def read(selection, start, stop, pipeline = Pipeline.new, opts = {}, &block)
|
337
|
+
if block_given?
|
338
|
+
yield pipeline
|
339
|
+
end
|
340
|
+
|
341
|
+
query = Query.new(Search.new("devices", selection),
|
342
|
+
Read.new(start, stop, opts[:limit]),
|
343
|
+
pipeline)
|
344
|
+
|
345
|
+
Cursor.new(Row, remoter, "/v2/read", query, media_types(:accept => [media_type("error", "v1"), media_type("datapoint-collection", "v2")],
|
346
|
+
:content => media_type("query", "v1")))
|
347
|
+
end
|
348
|
+
|
349
|
+
# Read the latest value from a set of Devices / Sensors, with an optional functional pipeline
|
350
|
+
# to transform the values.
|
351
|
+
#
|
352
|
+
# * +selection+ [Selection] - Device selection, describes which Devices / Sensors we should operate on
|
353
|
+
# * +pipeline+ [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.
|
354
|
+
#
|
355
|
+
# On success:
|
356
|
+
# - Return a Cursor of Row objects with only one Row inside
|
357
|
+
# On failure:
|
358
|
+
# - Raise an HttpException
|
359
|
+
#
|
360
|
+
# ==== Example
|
361
|
+
# # Find the latest DataPoints from Device 'bulding4567' Sensor 'temp1'
|
362
|
+
# rows = client.latest({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}})
|
363
|
+
# rows.each do |row|
|
364
|
+
# puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
|
365
|
+
# end
|
366
|
+
def latest(selection, pipeline = Pipeline.new, &block)
|
367
|
+
if block_given?
|
368
|
+
yield pipeline
|
369
|
+
end
|
370
|
+
|
371
|
+
query = Query.new(Search.new("devices", selection),
|
372
|
+
Single.new(false),
|
373
|
+
pipeline)
|
374
|
+
|
375
|
+
Cursor.new(Row, remoter, "/v2/single", query)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Delete datapoints by device and sensor key, start and stop date
|
379
|
+
#
|
380
|
+
# + *device_key* [String] - Device key to read from
|
381
|
+
# + *sensor_key* [String] - Sensor key to read from
|
382
|
+
# * +start+ [Time] - Read start interval
|
383
|
+
# * +stop+ [Time] - Read stop interval
|
384
|
+
#
|
385
|
+
# On success:
|
386
|
+
# _ Return a DeleteSummary describing the number of points deleted
|
387
|
+
# On failure:
|
388
|
+
# - Raise an HttpException
|
389
|
+
#
|
390
|
+
# ==== Example
|
391
|
+
# # Delete data from 'device1', 'temp' from 2013
|
392
|
+
# start = Time.utc(2013, 1, 1)
|
393
|
+
# stop = Time.utc(2013, 12, 31)
|
394
|
+
# summary = client.delete_datapoints('device1', 'temp', start, stop)
|
395
|
+
# puts "Deleted #{summary.deleted} points"
|
396
|
+
def delete_datapoints(device_key, sensor_key, start, stop)
|
397
|
+
delete_range = {:start => start.iso8601(3), :stop => stop.iso8601(3)}
|
398
|
+
result = remoter.delete("/v2/devices/#{URI.escape(device_key)}/sensors/#{URI.escape(sensor_key)}/datapoints", JSON.dump(delete_range))
|
399
|
+
case result.code
|
400
|
+
when HttpResult::OK
|
401
|
+
json = JSON.parse(result.body)
|
402
|
+
DeleteSummary.new(json['deleted'])
|
403
|
+
else
|
404
|
+
raise HttpException.new(result)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Convenience function to read from a single Device, and single Sensor
|
409
|
+
#
|
410
|
+
# + *device_key* [String] - Device key to read from
|
411
|
+
# + *sensor_key* [String] - Sensor key to read from
|
412
|
+
# * +start+ [Time] - Read start interval
|
413
|
+
# * +stop+ [Time] - Read stop interval
|
414
|
+
# * +pipeline+ [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.
|
415
|
+
#
|
416
|
+
# On success:
|
417
|
+
# - Return a Cursor of DataPoint objects.
|
418
|
+
# On failure:
|
419
|
+
# - Raise an HttpException
|
420
|
+
#
|
421
|
+
# ==== Example
|
422
|
+
# # Read from 'device1', 'temp1'
|
423
|
+
# start = Time.utc(2014, 1, 1)
|
424
|
+
# stop = Time.utc(2014, 1, 2)
|
425
|
+
# datapoints = client.read_device_sensor('device1', 'temp1', start, stop)
|
426
|
+
# datapoints.each do |point|
|
427
|
+
# puts "DataPoint ts: #{point.ts}, value: #{point.value}"
|
428
|
+
# end
|
429
|
+
def read_device_sensor(device_key, sensor_key, start, stop, pipeline = nil, &block)
|
430
|
+
selection = {:devices => {:key => device_key}, :sensors => {:key => sensor_key}}
|
431
|
+
read(selection, start, stop, pipeline).map do |row|
|
432
|
+
sub_key = row.values.map { |device_key, sensors| sensors.keys.first }.first || sensor_key
|
433
|
+
DataPoint.new(row.ts, row.value(device_key, sub_key))
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
private
|
438
|
+
|
439
|
+
def media_types(types)
|
440
|
+
{
|
441
|
+
"Accept" => types[:accept],
|
442
|
+
"Content-Type" => types[:content]
|
443
|
+
}
|
444
|
+
end
|
445
|
+
|
446
|
+
def media_type(media_resource, media_version, suffix = "json")
|
447
|
+
"#{MEDIA_PREFIX}.#{media_resource}.#{media_version}+#{suffix}"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|