SensorStream 0.0.4 → 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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/SensorStream.rb +173 -149
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b39b1d786a844a90ad09029f664b11ba6ac86bf
4
- data.tar.gz: 1923ab463642ce72360f5d5fcdef91f049877042
3
+ metadata.gz: 8c0fac5d01abfb17186ab0f582c555ed09b49ac2
4
+ data.tar.gz: b3f6a855df09dbaa26dfb80af910bdba4a809524
5
5
  SHA512:
6
- metadata.gz: dc0b813e4d2a64cc25450782471f6d6ec72ed087f048eeececde97f568844c1873369b7e25b530111d9b10980583bd1cfbc8bc387638bac2ef490b3c7845ad7c
7
- data.tar.gz: 4df0e7348503bf383b93c22230a69c67ad7ee134b7a403a651788679e9cf560f7b525d538c25397072c1d94f359d97fe73b12c0c31c9702798a8d6cae20ee6a2
6
+ metadata.gz: af878ffa2bf56e349b50340ab7323fbafdfc748ddb661efa5898610b5eee70ee7b864b10b92c69bbbbaa1265b65c6c7db96700d6b9144107f221de6625f2c675
7
+ data.tar.gz: 7af61bbeb3fe8e1a1d1fe37885a6b444503d407e23390673512125e90209a954cbbf960daa380bd684fb14721ea0a6f44ac0dd0362f0bf0fb452e38a30d1f3cc
data/lib/SensorStream.rb CHANGED
@@ -7,15 +7,18 @@ require 'pp'
7
7
  require 'base64'
8
8
  require 'open3'
9
9
  require 'date'
10
+ #require 'Zlib'
10
11
 
11
12
  module SensorStream
12
13
  attr_accessor :host_name, :port_number
13
14
 
15
+ # Allows the SensorStream server to be overridden
14
16
  def self.set_default_server
15
- @host_name = "dodeca.coas.oregonstate.edu"
17
+ @host_name = "sensorstream.cloudapp.net"
16
18
  @port_number = 80
17
19
  end
18
20
 
21
+ # Internally used to wrap HTTP puts with the requires headers
19
22
  def self.make_http_post(request = "", body_dict = {}, header_dict = {}, timeout = 600, debug = false)
20
23
  # Ensure that the hostname and port are set
21
24
  self.set_default_server unless !@host_name.nil?
@@ -23,35 +26,42 @@ module SensorStream
23
26
  # Make sure that, at a minimum, the content type is set in the header
24
27
  header_dict["Content-Type"] = "application/json";
25
28
 
26
- puts header_dict unless !debug
27
-
28
29
  uri = URI.parse("http://" + @host_name + request);
29
- puts "POST to #{uri.to_s}" unless !debug
30
+ if (debug)
31
+ puts "POST to #{uri.to_s}"
32
+ puts "Header: #{header_dict}"
33
+ puts "Body: #{JSON.generate(body_dict)}"
34
+ end
30
35
 
31
36
  http = Net::HTTP.new(@host_name, @port_number);
32
37
  http.read_timeout = timeout; # 10 minute default timeout
33
38
  http.post(uri.request_uri, JSON.generate(body_dict), header_dict);
39
+ # This is very important: we're making use of the implied return of ruby, here!!!
34
40
  end
35
41
 
42
+ # Internally used to wrap HTTP gets with the required headers
36
43
  def self.make_http_get(request = "", header_dict = {}, timeout = 600, debug = false)
37
44
  # Ensure that the hostname and port are set
38
45
  self.set_default_server unless !@host_name.nil?
39
46
 
40
- puts header_dict unless !debug
41
47
 
42
48
  uri = URI.parse("http://" + @host_name + request);
43
- puts "GET from #{uri.to_s}" unless !debug
49
+ if (debug)
50
+ puts "GET from #{uri.to_s}"
51
+ puts "Header: #{header_dict.to_s}"
52
+ end
44
53
 
45
54
  http = Net::HTTP.new(@host_name, @port_number);
46
55
  http.read_timeout = 600; # 10 minute timeout
47
56
  http.get(uri.request_uri, header_dict);
57
+ # This is very important: we're making use of the implied return of ruby, here!!!
48
58
  end
49
59
 
50
60
  # create a new device on the server
51
61
  def self.create_device(user_name, device_name, description)
52
- dict = {"UserName" => user_name,
53
- "DeviceName" => device_name,
54
- "Description" => description}
62
+ dict = {"username" => user_name,
63
+ "devicename" => device_name,
64
+ "description" => description}
55
65
 
56
66
  resp = make_http_post("/device.ashx?create=", dict)
57
67
 
@@ -62,6 +72,7 @@ module SensorStream
62
72
  end
63
73
  end
64
74
 
75
+ # Delete a device with a given key from the server
65
76
  def self.delete_device_with_key(device_key)
66
77
  puts "Deleting device #{device_key}"
67
78
  resp = make_http_post("/device.ashx?delete=device", {}, { "key" => device_key })
@@ -74,10 +85,12 @@ module SensorStream
74
85
  end
75
86
  end
76
87
 
88
+ # Deletes a device from the server given a ruby object with a key
77
89
  def self.delete_device(device)
78
90
  delete_device_with_key(device.key)
79
91
  end
80
92
 
93
+ # Delete a stream from a device given a streamID and device key
81
94
  def self.delete_stream_with_key(streamID, device_key)
82
95
  puts "Deleting Stream #{streamID}"
83
96
  resp = make_http_post("/stream.ashx?delete=#{streamID}", {}, { "key" => device_key })
@@ -90,6 +103,7 @@ module SensorStream
90
103
  end
91
104
  end
92
105
 
106
+ # Reads a list of devices from the server
93
107
  def self.get_devices()
94
108
  resp = make_http_get("/device.ashx?getdevices=");
95
109
 
@@ -97,22 +111,38 @@ module SensorStream
97
111
  return nil;
98
112
  else
99
113
  devices = [];
100
- JSON.parse(resp.body)["Devices"].each { |device|
101
- devices << SensorStream::Device.new(device["UserName"],
102
- device["DeviceName"],
103
- device["Description"])
104
- devices.last.get_streams
114
+ JSON.parse(resp.body).each { |device|
115
+ devices << SensorStream::Device.new(device["username"],
116
+ device["devicename"],
117
+ device["description"])
118
+ #devices.last.get_streams
105
119
  }
106
120
  return devices;
107
121
  end
108
122
  end
109
123
 
124
+ # Retreives a device given a device name
125
+ def self.get_device_by_name(name)
126
+ devices = SensorStream.get_devices
127
+ device = nil
128
+ devices.each { |test_device|
129
+ if (test_device.device_name == name)
130
+ return test_device
131
+ end
132
+ }
133
+
134
+ return nil
135
+ end
136
+
137
+ # Implementation of the device class
110
138
  class Device
111
139
  # Each of the qualities of a device are immutable.
112
140
  # The sensor stream API does not support changing device attributes.
113
141
  attr_reader :user_name, :device_name, :description
142
+ # It is possible to change the streams and assign a key later, however.
114
143
  attr_accessor :streams, :key
115
144
 
145
+ # Initialize a new SensorStream device object
116
146
  def initialize(newUserName, newDeviceName, newDescription, newStreams = [], newKey = "")
117
147
  @key = newKey
118
148
  @device_name = newDeviceName
@@ -121,14 +151,26 @@ module SensorStream
121
151
  @streams = newStreams
122
152
  end
123
153
 
154
+ # Create a useful (to a human) representation of the device for printing
124
155
  def to_s
125
156
  "Name: #{@device_name} \n\tUser: #{@user_name} \n\tDescription: #{@description} \n\tKey: #{@key}"
126
157
  end
127
158
 
128
- def create_complex_stream(name, description, elements)
129
- dict = {"Name" => name,
130
- "Description" => description,
131
- "Streams" => elements};
159
+ # Create a new stream on this device
160
+ def create_stream(name, description, elements)
161
+
162
+ # Enforce lower-case values on all given element keys
163
+ tempElements = []
164
+ elements.each { |element|
165
+ new_hash = element.each_with_object({}) do |(k, v), h|
166
+ h[k.downcase] = v
167
+ end
168
+ tempElements << new_hash
169
+ }
170
+
171
+ dict = {"name" => name,
172
+ "description" => description,
173
+ "streams" => tempElements};
132
174
 
133
175
  resp = SensorStream.make_http_post("/stream.ashx?create=", dict, { "key" => @key })
134
176
 
@@ -136,28 +178,14 @@ module SensorStream
136
178
  STDERR.puts "Error creating SensorStream device! (#{resp.code})\n#{resp.body}"
137
179
  return nil
138
180
  else
139
- return ComplexStream.new(self, JSON.parse(resp.body)["StreamID"], name, description, elements)
140
- end
141
- end
142
-
143
- def create_stream(name, type, units, description)
144
- dict = {"Name" => name,
145
- "Type" => type,
146
- "Units" => units,
147
- "Description" => description}
148
-
149
- resp = SensorStream.make_http_post("/stream.ashx?create=", dict, { "key" => @key })
150
-
151
- if (resp.code != "200")
152
- STDERR.puts "Error creating SensorStream stream! (#{resp.code})\n#{resp.body}"
153
- else
154
- Stream.new(self, JSON.parse(resp.body)["StreamID"], name, type, units, description)
181
+ return Stream.new(self, JSON.parse(resp.body)["streamid"], name, description, tempElements)
155
182
  end
156
183
  end
157
184
 
158
- def delete_stream_with_id(streamID)
159
- puts "Deleting Stream #{streamID}"
160
- resp = SensorStream.make_http_post("/stream.ashx?delete=#{streamID}", {}, { "key" => @key })
185
+ # Delete a stream from this device using a stream ID
186
+ def delete_stream_with_id(streamid)
187
+ puts "Deleting Stream #{streamid}"
188
+ resp = SensorStream.make_http_post("/stream.ashx?delete=#{streamid}", {}, { "key" => @key })
161
189
 
162
190
  if (resp.code != "200")
163
191
  STDERR.puts "Error deleting stream #{streamID} (#{resp.code})\n{resp.body}"
@@ -167,37 +195,32 @@ module SensorStream
167
195
  end
168
196
  end
169
197
 
198
+ # Delete a stream from this device using a ruby object
170
199
  def delete_stream(stream)
171
- delete_stream_with_id(stream.streamID)
200
+ delete_stream_with_id(stream.streamid)
172
201
  end
173
202
 
203
+ # Delete all the streams belonging to this device
174
204
  def delete_streams
175
205
  puts "Deleting all streams"
176
206
  resp = SensorStream.make_http_post("/device.ashx?delete=streams", {}, { "key" => @key }, 600, true)
177
207
 
178
208
  if (resp.code != "200")
179
- STDERR.puts "Error deleting stream #{streamID} (#{resp.code})\n{resp.body}"
209
+ STDERR.puts "Error deleting stream #{streamid} (#{resp.code})\n{resp.body}"
180
210
  false
181
211
  else
182
212
  true
183
213
  end
184
214
  end
185
215
 
216
+ # Get a list of all the streams for this device
186
217
  def get_streams
187
218
  resp = SensorStream.make_http_get("/stream.ashx?getstreams=#{URI::encode(@device_name)}&user=#{URI::encode(@user_name)}")
188
219
 
189
220
  if (resp.code == "200")
190
221
  streams = []
191
- JSON.parse(resp.body)["Streams"].each do |streamDict|
192
- # Detect whether the stream we just got is complex
193
- if(streamDict["Streams"].nil?)
194
- streams << Stream.new(self,
195
- streamDict["StreamID"], streamDict["Name"],
196
- streamDict["Type"], streamDict["Units"],
197
- streamDict["Description"])
198
- else
199
- streams << ComplexStream.new(self, streamDict["StreamID"], streamDict["Name"], streamDict["Description"], streamDict["Streams"])
200
- end
222
+ JSON.parse(resp.body)["streams"].each do |streamDict|
223
+ streams << Stream.new(self, streamDict["streamid"], streamDict["name"], streamDict["description"], streamDict["streams"])
201
224
  end
202
225
  return streams;
203
226
  else
@@ -205,151 +228,152 @@ module SensorStream
205
228
  return nil
206
229
  end
207
230
  end
231
+
232
+ # Get a stream by name
233
+ def get_stream_by_name(name)
234
+ streams = self.get_streams
235
+ stream = nil
236
+ streams.each { |test_stream|
237
+ if (test_stream.name == name)
238
+ return test_stream
239
+ end
240
+ }
241
+
242
+ return nil
243
+ end
208
244
  end
209
245
 
210
246
  # The simple stream class is the base class for complex streams
211
247
  class Stream
212
248
  # Each of the qualities of the stream are immutable.
213
249
  # The sensor stream API does not support changing stream attributes.
214
- attr_reader :device, :streamID, :name, :type, :units, :description
250
+ attr_reader :device, :streamid, :name, :description, :elements
215
251
 
216
- def initialize(newDevice, newStreamID, newName, newType, newUnits, newDescription)
217
- @device = newDevice
218
- @streamID = newStreamID
219
- @name = newName
220
- @type = newType
221
- @units = newUnits
222
- @description = newDescription
223
- @deferedEvents = [];
224
- end
252
+ # Initalize the fields of the stream object
253
+ def initialize(newDevice, newStreamid, newName, newDescription, newElements)
254
+ @device = newDevice
255
+ @streamid = newStreamid
256
+ @name = newName
257
+ @elements = newElements
258
+ @description = newDescription
259
+ @deferredEvents = []
260
+ end
225
261
 
262
+ # Create a useful (to a human) representation of the device for printing
226
263
  def to_s
227
- "Name: #{@name} \n\tStreamID: #{@streamID} \n\tType: #{@type} " +
228
- "\n\tUnits: #{@units} \n\tDescription: #{@description}" +
229
- "\n\tDevice: #{device.device_name}"
264
+ elementsString = "";
265
+ @elements.each do |element|
266
+ elementsString += "\n\t\t#{element["name"]}" +
267
+ "\n\t\t\tTypes: #{element["type"]}" +
268
+ "\n\t\t\tUnits: #{element["units"]}"
269
+ end
270
+
271
+ "Name: #{@name} \n\tStreamID: #{@streamid} " +
272
+ "\n\tElements: #{elementsString} " +
273
+ "\n\tDescription: #{@description}" +
274
+ "\n\tDevice: #{device.device_name}"
230
275
  end
231
276
 
232
- def publish_event(value, time = nil)
233
- dict = { "StreamID" => @streamID, "value" => value.to_s }
234
- dict["Time"] = time.strftime("%FT%T.%N%:z") unless time.nil?
235
-
236
- #puts dict
277
+ # Publish an event immediately. If it doesn't succeed, add to the deferred queue.
278
+ def publish_event(values, time=nil)
279
+ dict = {"streamid" => @streamid, "values" => values};
280
+ dict["time"] = time.strftime("%FT%T.%N%:z") unless time.nil?
281
+ attemptTime = Time.now
237
282
 
238
283
  resp = SensorStream.make_http_post("/data.ashx?create=", dict, { "key" => @device.key })
239
-
284
+
240
285
  if (resp.code != "200")
241
- STDERR.puts "Error publishing SensorStream event! (#{resp.code})\n#{resp.body}"
242
- return nil
286
+ STDERR.puts "Error publishing SensorStream event! (#{resp.code})\n#{resp.body} -- deferring message"
287
+ publish_event_deferred(values, attemptTime)
288
+ return nil;
243
289
  else
244
- DateTime.strptime(JSON.parse(resp.body)["Time"],"%FT%T.%N%:z")
290
+ DateTime.strptime(JSON.parse(resp.body)["time"],"%FT%T.%N%:z")
245
291
  end
246
292
  end
247
293
 
248
- def publish_event_defered(value, time = nil)
294
+ # Add an event to the deferred message queue
295
+ def publish_event_deferred(values, time=nil)
249
296
  time ||= Time.now
250
- @defered_events << { "StreamID" => @streamID,
251
- "value" => value.to_s,
252
- "Time" => time.strftime("%FT%T.%N%:z") }
297
+
298
+ if(@deferred_events.nil?)
299
+ @deferred_events = []
300
+ end
301
+
302
+ @deferred_events << { "streamid" => @streamid,
303
+ "values" => values,
304
+ "time" => time.strftime("%FT%T.%N%:z") }
253
305
  end
254
306
 
255
- def publish_defered_events
256
- # Ensure that there are defered messages to send
257
- if(@deferedEvents.nil? || @deferedEvents.empty?)
258
- @deferedEvents = [] unless !@deferedEvents.nil?
259
- return
307
+ # Publish all the deferred events (if there are any)
308
+ def publish_deferred_events
309
+ # Ensure that there are deferred messages to send
310
+ if(@deferred_events.nil? || @deferred_events.empty?)
311
+ @deferred_events = [] unless !@deferred_events.nil?
312
+ puts "No events to publish"
313
+ if (@deferred_events.nil?)
314
+ puts "Deferred events nil."
315
+ elsif (@deferred_events.empty?)
316
+ puts "Deferred events empty."
317
+ end
318
+ return 0
260
319
  end
261
320
 
262
- resp = SensorStream.make_http_post("/data.ashx?create=", @defered_events, { "key" => @device.key })
321
+ resp = SensorStream.make_http_post("/data.ashx?create=", @deferred_events, { "key" => @device.key })
263
322
 
264
323
  # If the send failed, keep the messages
265
324
  if resp.code != "200"
266
- puts "Unable to publish defered events, keeping events."
325
+ puts "Unable to publish deferred events, keeping events."
326
+ return 0
267
327
  else
268
- count = @deferedEvents.count
269
- @deferedEvents = []
328
+ count = @deferred_events.count
329
+ @deferred_events = []
270
330
  return count
271
331
  end
272
332
  end
273
333
 
334
+ # Download a list of events from the server from this stream
274
335
  def get_events(count = 1, start_time = nil, end_time = nil)
275
- base = "/data.ashx?getdata=#{@streamID}"
336
+ base = "/data.ashx?getdata=#{@streamid}"
276
337
  base += "&start=#{start_time.strftime("%FT%T.%N%:z")}" unless start_time.nil?
277
338
  base += "&end=#{ end_time.strftime("%FT%T.%N%:z")}" unless end_time.nil?
278
339
  base += "&count=#{count}"
279
340
 
280
- resp = SensorStream.make_http_get(base)
341
+ resp = SensorStream.make_http_get(base, {"Accept-Encoding" => "gzip"}, 600, true)
281
342
 
282
343
  if resp.code != "200"
283
- puts "Unable to load events from stream"
344
+ STDERR.puts "Unable to load events from stream: #{resp.body}"
284
345
  return nil
285
346
  end
286
347
 
348
+ if (resp["Content-Encoding"] == "gzip")
349
+ puts "Content was zipped, inflating"
350
+ content = Zlib::Inflate.inflate(resp.body)
351
+ puts resp.body
352
+ elsif
353
+ content = resp.body
354
+ end
355
+
287
356
  # We're only interested in the values and their timestamps
288
357
  # but the server gives us the device information anyway
289
358
  # Return only the Data array from the dictionary
290
- #puts resp.body
291
- data = JSON.parse(resp.body)["Stream"]["Data"]
292
- if data.class == Array
293
- data.each { |event_dict|
294
- #puts event_dict
295
- # Replace the time string with Ruby time objects
296
- event_dict["Time"] = DateTime.strptime(event_dict["Time"],"%FT%T.%N%:z")
297
- #puts event_dict
298
- }
299
- return data
300
- else
301
- return [event_dict["Time"] = DateTime.strptime(event_dict["Time"],"%FT%T.%N%:z")];
302
- end
303
- end
304
- end
305
-
306
- class ComplexStream < Stream
307
- #attr_protected :type, :units
308
- attr_reader :elements
309
- def initialize(newDevice, newStreamID, newName, newDescription, newElements)
310
- @device = newDevice
311
- @streamID = newStreamID
312
- @name = newName
313
- @elements = newElements
314
- @description = newDescription
315
- @deferedEvents = []
316
- end
317
-
318
- def to_s
319
- elementsString = "";
320
- @elements.each do |element|
321
- elementsString += "\n\t\t#{element["Name"]}" +
322
- "\n\t\t\tTypes: #{element["Type"]}" +
323
- "\n\t\t\tUnits: #{element["Units"]}"
324
- end
325
-
326
- "Name: #{@name} \n\tStreamID: #{@streamID} " +
327
- "\n\tElements: #{elementsString} " +
328
- "\n\tDescription: #{@description}" +
329
- "\n\tDevice: #{device.device_name}"
330
- end
331
-
332
- def publish_event(values, time=nil)
333
- dict = {"StreamID" => @streamID, "Values" => values};
334
- dict["Time"] = time.strftime("%FT%T.%N%:z") unless time.nil?
335
-
336
- #puts dict
359
+ streams = (JSON.parse(resp.body)["streams"])
360
+ data = nil;
361
+ streams.each { |stream|
362
+ if (stream["streamid"] == @streamid)
363
+ data = stream["data"]
364
+ end
365
+ }
337
366
 
338
- resp = SensorStream.make_http_post("/data.ashx?create=", dict, { "key" => @device.key })
339
-
340
- if (resp.code != "200")
341
- STDERR.puts "Error publishing SensorStream event! (#{resp.code})\n#{resp.body}"
342
- return nil;
343
- else
344
- DateTime.strptime(JSON.parse(resp.body)["Time"],"%FT%T.%N%:z")
367
+ if (data.nil?)
368
+ STDERR.puts "Can't find data in get events for this stream"
369
+ return nil
345
370
  end
371
+
372
+ data.each { |event_dict|
373
+ # Replace the time string with Ruby time objects
374
+ event_dict["time"] = DateTime.strptime(event_dict["time"],"%FT%T.%N%:z")
375
+ }
376
+ return data
346
377
  end
347
-
348
- def publish_event_defered(values, time=nil)
349
- time ||= Time.now
350
- @defered_events << { "StreamID" => @streamID,
351
- "Values" => values,
352
- "Time" => time.strftime("%FT%T.%N%:z") }
353
- end
354
- end
378
+ end
355
379
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: SensorStream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Dillon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-30 00:00:00.000000000 Z
11
+ date: 2014-05-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby implementation of the SensorStream API
14
14
  email: william@housedillon.com
@@ -37,7 +37,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
37
  version: '0'
38
38
  requirements: []
39
39
  rubyforge_project:
40
- rubygems_version: 2.0.3
40
+ rubygems_version: 2.0.14
41
41
  signing_key:
42
42
  specification_version: 4
43
43
  summary: SensorStream API