argos-ruby 1.0.5 → 1.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.
data/lib/argos/diag.rb CHANGED
@@ -5,15 +5,12 @@ module Argos
5
5
  # Usage
6
6
  #
7
7
  # diag = Argos::Diag.new
8
- # diag.log = Logger.new(STDERR)
9
8
  # puts diag.parse(filename).to_json
10
9
  #
11
- # For information about Argos, see: http://www.argos-system.org
12
- #
13
10
  # @author Espen Egeland
14
11
  # @author Conrad Helgeland
15
12
  class Diag < Array
16
- include Argos
13
+ include Argos::Ascii
17
14
 
18
15
  LOCATION_CLASS = ["3", "2", "1", "0", "A", "B", "Z"]
19
16
 
@@ -56,6 +53,7 @@ module Argos
56
53
  def initialize
57
54
  @errors = []
58
55
  @programs = []
56
+ @log = Logger.new(STDERR)
59
57
  end
60
58
 
61
59
  def filter?
@@ -235,12 +233,12 @@ module Argos
235
233
  sensor_data: sensor_data,
236
234
  technology: "argos",
237
235
  type: type,
238
- location: "file://"+filename,
236
+ file: "file://"+filename,
239
237
  source: "#{sha1}",
240
238
  }
241
239
 
242
240
  idbase = diag.clone
243
- idbase.delete :location
241
+ idbase.delete :file
244
242
  id = Digest::SHA1.hexdigest(idbase.to_json)
245
243
 
246
244
  diag[:parser] = Argos.library_version
@@ -328,8 +326,6 @@ end
328
326
  # 168 64 30
329
327
 
330
328
 
331
-
332
-
333
329
  #200910Oct.DIA
334
330
  #: Line: 26132 Invalid format:
335
331
  #52061 Date : 03.10.09 09:47:05 LC : Z IQ : 38 Lat1 : 76.998N Lon1 : 22.398W Lat2 : 74.370N Lon2 : 10.000 Nb mes : 003 Nb mes>-120dB : 000 Best level : -129 dB Pass duration : 100s NOPC : 0 Calcul freq : 401 677501.2 Hz Altitude : 0 m 40 35 00 00 00 00 00 240 00 00 00 00 64 00 197
data/lib/argos/ds.rb CHANGED
@@ -5,17 +5,13 @@ module Argos
5
5
  # Usage
6
6
  #
7
7
  # ds = Argos::Ds.new
8
- # ds.log = Logger.new(STDERR)
9
8
  # puts ds.parse(filename).to_json
10
9
  #
11
- #
12
- # For information about Argos, see: http://www.argos-system.org
13
- #
14
10
  # @author Espen Egeland
15
11
  # @author Conrad Helgeland
16
- # @todo errors => warn or remove unless asked for? (debug)
12
+ #
17
13
  class Ds < Array
18
- include Argos
14
+ include Argos::Ascii
19
15
 
20
16
  attr_writer :log, :filename, :bundle
21
17
 
@@ -29,6 +25,7 @@ module Argos
29
25
 
30
26
  def initialize
31
27
  @errors = []
28
+ @log = Logger.new(STDERR)
32
29
  end
33
30
 
34
31
  def filter?
@@ -312,7 +309,7 @@ module Argos
312
309
  m = m.merge(measurement)
313
310
  m = m.merge ({ technology: "argos",
314
311
  type: type,
315
- location: "file://"+filename,
312
+ file: "file://"+filename,
316
313
  source: sha1
317
314
  })
318
315
 
@@ -354,7 +351,7 @@ module Argos
354
351
 
355
352
  idbase = m.clone
356
353
  idbase.delete :errors
357
- idbase.delete :location
354
+ idbase.delete :file
358
355
  idbase.delete :warn
359
356
 
360
357
  id = Digest::SHA1.hexdigest(idbase.to_json)
@@ -1,4 +1,8 @@
1
1
  module Argos
2
2
  class Exception < ::Exception
3
3
  end
4
+ class SoapException < Exception
5
+ end
6
+ class SoapFault < SoapException
7
+ end
4
8
  end
data/lib/argos/soap.rb ADDED
@@ -0,0 +1,372 @@
1
+ root = File.dirname(File.realpath(__FILE__))+"/../.."
2
+
3
+ Dir.chdir root do
4
+ begin
5
+ require "savon"
6
+ rescue LoadError
7
+ # Prepend Savon lib to path, bundler seems not work
8
+ unless defined? Savon
9
+ # Cannot use #find_name returns []
10
+ # raise Bundler.rubygems.find_name('savon').to_json #.first.full_gem_path
11
+ savonbundle = `bundle show savon`.chomp
12
+ $LOAD_PATH.unshift(savonbundle+"/lib")
13
+ require_relative "#{savonbundle}/lib/savon"
14
+ end
15
+ end
16
+ end
17
+
18
+ module Argos
19
+ class Soap
20
+
21
+ # A "simple" Soap client for Argos-system satellite tracking webservice
22
+ # http://wanderingbarque.com/nonintersecting/2006/11/15/the-s-stands-for-simple/
23
+
24
+ # client [Savon]
25
+ # request [String] Soap:Envelope (XML request body)
26
+ # response [Savon::Response]
27
+ # operation [Savon::Operation]
28
+ # xml [String] (Extracted, inner) XML
29
+ attr_accessor :client, :request, :response, :operation, :xml, :filter,
30
+ :platformId, :programNumber, :nbDaysFromNow, :period
31
+
32
+ attr_writer :username, :password
33
+
34
+ URI = "http://ws-argos.cls.fr/argosDws/services/DixService"
35
+ # Alternative: "http://ws-argos.clsamerica.com/argosDws/services/DixService"
36
+
37
+ WSDL = "#{URI}?wsdl"
38
+
39
+ ARGOS_NS = "http://service.dataxmldistribution.argos.cls.fr/types"
40
+ SOAP_NS = "http://www.w3.org/2003/05/soap-envelope"
41
+
42
+ NAMESPACES = {
43
+ "soap" => SOAP_NS,
44
+ "argos" => ARGOS_NS
45
+ }
46
+
47
+ # Constructor
48
+ # soap = Argos::Soap.new({username: "argos-system-user", password: "argos-system-pw"})
49
+ def initialize(config={})
50
+ config.each do |k,v|
51
+ case k.to_sym
52
+ when :username
53
+ @username=v
54
+ when :password
55
+ @password=v
56
+ when :wsdl
57
+ @wsdl=v
58
+ when :programNumber
59
+ @programNumber = v
60
+ when :platformId
61
+ @platformId = v
62
+ when :nbDaysFromNow
63
+ @nbDaysFromNow = v.to_i
64
+ when :period
65
+ @period = v
66
+ when :filter
67
+ @filter = v
68
+ else
69
+ #raise ArgumentError, "Unkown config key: #{k}"
70
+ end
71
+ end
72
+ end
73
+
74
+ # Build baseRequest Hash
75
+ # The service requires programNumber or PlatformId, but if you do not provide any,
76
+ # this method will call the service (@see #programs) and get the current user's programs
77
+ # @return [Hash]
78
+ def baseRequest
79
+ # if override key is platformId... delete programNumber...
80
+ # if override key is period... delete nbDaysFromNow...
81
+ baseRequest = { username: _username, password: _password }
82
+
83
+ # First choice (program or platform)
84
+ if @programNumber.nil? and @platformId.nil?
85
+ # Fetch all programs if neither is provided
86
+ baseRequest[:programNumber] = programs.map {|p|p.to_s}.join(",")
87
+ elsif @programNumber =~ /\d+/ and @platformId =~ /\d+/
88
+ raise "Cannot provide both programNumber and platformId"
89
+ elsif @programNumber =~ /\d+/
90
+ baseRequest[:programNumber] = @programNumber
91
+ elsif @platformId =~ /\d+/
92
+ baseRequest[:platformId] = @platformId
93
+ end
94
+
95
+ # 2nd choice (time)
96
+ if @nbDaysFromNow.nil? and @period.nil?
97
+ # Default to 20 days of data (the maximum)
98
+ baseRequest[:nbDaysFromNow] = 20
99
+ elsif @nbDaysFromNow =~ /\d+/ and not @period.nil?
100
+ raise "Cannot provide both nbDaysFromNow and period"
101
+ elsif @nbDaysFromNow.to_s =~ /\d+/
102
+ baseRequest[:nbDaysFromNow] = @nbDaysFromNow.to_i
103
+ else
104
+ baseRequest[:period] = @period
105
+ end
106
+
107
+ baseRequest = baseRequest.merge({
108
+ #<xs:element minOccurs="0" name="referenceDate" type="tns:referenceDateType"/>
109
+ #<xs:element minOccurs="0" name="locClass" type="xs:string"/>
110
+ #<xs:element minOccurs="0" name="geographicArea" type="xs:string"/>
111
+ #<xs:element minOccurs="0" name="compression" type="xs:int"/>
112
+ #<xs:element minOccurs="0" name="mostRecentPassages" type="xs:boolean"/>
113
+ })
114
+ baseRequest
115
+
116
+ end
117
+
118
+ # @return [Savon]
119
+ def client
120
+ @client ||= begin
121
+
122
+ tmpwsdl = "/tmp/argos-soap.wsdl"
123
+ if File.exists?(tmpwsdl)
124
+ uri = tmpwsdl
125
+ else
126
+ uri = @wsdl||=WSDL
127
+ end
128
+
129
+ client = ::Savon.new(uri)
130
+ if not File.exists?(tmpwsdl)
131
+ response = ::Net::HTTP.get_response(::URI.parse(WSDL))
132
+ File.open(tmpwsdl, "wb") { |file| file.write(response.body)}
133
+ end
134
+ client
135
+ end
136
+ end
137
+
138
+ def filter?
139
+ not @filter.nil? and filter.respond_to?(:call)
140
+ end
141
+
142
+ # @return [String]
143
+ def getCsv
144
+ o = _operation(:getCsv)
145
+ o.body = { csvRequest: baseRequest.merge(
146
+ showHeader: true).merge(xmlRequest)
147
+ }
148
+ @response = o.call
149
+ @request = o.build
150
+ @text = _extract_escaped_xml("csvResponse").call(response)
151
+ end
152
+
153
+ # @return [Hash]
154
+ def getKml
155
+ _call_xml_operation(:getKml, { kmlRequest: baseRequest.merge(xmlRequest)}, _extract_escaped_xml("kmlResponse"))
156
+ end
157
+
158
+ # @return [Hash]
159
+ # {"data":{"program":[{"programNumber":"9660","platform":[{ .. },{ .. }]}],"@version":"1.0"}}
160
+ # Each platform Hash (.. above): {"platformId":"129990","lastLocationClass":"3","lastCollectDate":"2013-10-03T08:32:24.000Z","lastLocationDate":"2013-05-22T04:55:15.000Z","lastLatitude":"47.67801","lastLongitude":"-122.13419"}
161
+ def getPlatformList
162
+ _call_xml_operation(:getPlatformList, { platformListRequest:
163
+ # Cannot use #baseRequest here because that methods calls #programs which also calls #getPlatformList...
164
+ { username: _username, password: _password },
165
+ }, _extract_escaped_xml("platformListResponse"))
166
+ end
167
+
168
+ # @return [Hash]
169
+ def getXml
170
+ _call_xml_operation(:getXml,
171
+ { xmlRequest: baseRequest.merge(xmlRequest)},
172
+ _extract_escaped_xml("xmlResponse"))
173
+ end
174
+
175
+ # @return [Hash]
176
+ def getStreamXml
177
+ _call_xml_operation(:getStreamXml,
178
+ { streamXmlRequest: baseRequest.merge(xmlRequest)},
179
+ _extract_motm)
180
+ end
181
+
182
+ # @return [Hash]
183
+ def getXsd
184
+ _call_xml_operation(:getXsd, { xsdRequest: {} }, _extract_escaped_xml("xsdResponse"))
185
+ end
186
+
187
+ # @return [Hash]
188
+ # choice: programNumber | platformId | wmo*
189
+ # nbMaxObs
190
+ def getObsXml
191
+ _call_xml_operation(:getObsXml,
192
+ { observationRequest: baseRequest.merge(xmlRequest)},
193
+ _extract_escaped_xml("observationResponse"))
194
+ end
195
+
196
+ # @return [Text]
197
+ # choice: programNumber | platformId | wmo*
198
+ # nbMaxObs
199
+ def getObsCsv
200
+ o = _operation(:getObsCsv)
201
+ o.body = { observationRequest: baseRequest.merge(xmlRequest)
202
+ }
203
+ @response = o.call
204
+ @request = o.build
205
+ @text = _extract_escaped_xml("observationResponse").call(response)
206
+ end
207
+
208
+ # @return [Array]
209
+ def platforms(programNumber=nil)
210
+ platforms = []
211
+ programs = getPlatformList["data"]["program"]
212
+ if programNumber.to_s =~ /\d+/
213
+ programs.select! {|p| p["programNumber"].to_i == programNumber.to_i }
214
+ end
215
+ programs.each do |program|
216
+ platforms += program["platform"].map {|p| p["platformId"].to_i}
217
+ end
218
+ platforms
219
+ end
220
+
221
+ def period(startDate, endDate)
222
+ { startDate: startDate, endDate: endDate }
223
+ end
224
+
225
+ # @return [Array]
226
+ def programs
227
+ getPlatformList["data"]["program"].map {|p| p["programNumber"].to_i }
228
+ end
229
+
230
+ # @return [String]
231
+ def raw
232
+ response.raw
233
+ end
234
+
235
+ # @return [String]
236
+ def request
237
+ operation.build
238
+ end
239
+
240
+ # @return [Hash] {"DixService":{"ports":{"DixServicePort":{"type":"http://schemas.xmlsoap.org/wsdl/soap12/","location":"http://ws-argos.cls.fr/argosDws/services/DixService"}}}}
241
+ def services
242
+ client.services
243
+ end
244
+
245
+ # @return [String]
246
+ def text
247
+ @text||=""
248
+ end
249
+
250
+ # @return [Array] [:getCsv, :getStreamXml, :getKml, :getXml, :getXsd, :getPlatformList, :getObsCsv, :getObsXml]
251
+ def operations
252
+ @response = client.operations(:DixService, :DixServicePort)
253
+ end
254
+
255
+ def namespaces
256
+ NAMESPACES
257
+ end
258
+
259
+ def xmlRequest
260
+ {
261
+ displayLocation: true,
262
+ displayDiagnostic: true,
263
+ displayMessage: true,
264
+ displayCollect: true,
265
+ displayRawData: true,
266
+ displaySensor: true,
267
+ #argDistrib: "",
268
+ displayImageLocation: true,
269
+ displayHexId: true
270
+ }
271
+ end
272
+
273
+ protected
274
+
275
+ # Build and call @operation, set @response, @request, and @xml
276
+ # @raise on faults
277
+ # @return [Hash]
278
+ def _call_xml_operation(op_sym, body, extract=nil)
279
+ @operation = _operation(op_sym)
280
+ @operation.body = body
281
+ @response = operation.call
282
+
283
+ # Check for http errors?
284
+
285
+ # Handle faults (before extracting data)
286
+ _envelope.xpath("soap:Body/soap:Fault", namespaces).each do | fault |
287
+ raise fault.to_s
288
+ end
289
+
290
+ # Extract data
291
+ if extract.respond_to?(:call)
292
+ @xml = extract.call(response)
293
+ else
294
+ @xml = response.raw
295
+ end
296
+
297
+ # Handle errors
298
+ ng = Nokogiri.XML(xml)
299
+ ng.xpath("/data/errors/error").each do | error |
300
+ raise error.to_s
301
+ # FIXME Custom exception so that it mighht be trapped on raw responses...
302
+ end
303
+
304
+ # Validation
305
+ # Validation against XSD does not work: ["Element 'data': No matching global declaration available for the validation root."]
306
+ # schema = Nokogiri::XML::Schema(File.read("/tmp/argos-data.xsd"))
307
+ # v = schema.validate(ng)
308
+
309
+ # Convert XML to Hash
310
+ nori = Nori.new
311
+ nori.parse(xml)
312
+ end
313
+
314
+ # This is a shame, but who's to blame when there's multiple XML prologs (the second is escaped) and even mix of (declared) encodings (UTF-8 in soap envelope, ISO-8859-1 inside)
315
+ # Note: the inner data elements are non-namespaced (see da), so that recreating as proper XML would need to set xmlns=""
316
+ def _extract_escaped_xml(responseElement)
317
+ lambda {|response| CGI.unescapeHTML(response.raw.split("<#{responseElement} xmlns=\"http://service.dataxmldistribution.argos.cls.fr/types\"><return>")[1].split("</return>")[0])}
318
+ end
319
+
320
+ # This is proof-of-concept quality code.
321
+ # @todo Need to extract boundary and start markers from Content-Type header:
322
+ # Content-Type: multipart/related; type="application/xop+xml"; boundary="uuid:14b8db9f-a393-4786-be3f-f0f7b12e14a2"; start="<root.message@cxf.apache.org>"; start-info="application/soap+xml"
323
+
324
+ # @return [String]
325
+
326
+ def _extract_motm
327
+ lambda {|response|
328
+ # Scan for MOTM signature --uuid:*
329
+ if response.raw =~ (/^(--[\w:-]+)--$/)
330
+ # Get the last message, which is -2 because of the trailing --
331
+ xml = response.raw.split($1)[-2].strip
332
+
333
+ # Get rid of HTTP headers
334
+ if xml =~ /\r\n\r\n[<]/
335
+ xml = xml.split(/\r\n\r\n/)[-1]
336
+ end
337
+
338
+ else
339
+ raise "Cannot parse MOTM"
340
+ end
341
+ }
342
+ end
343
+
344
+ # @return [Nokogiri:*]
345
+ def _envelope
346
+ ng = Nokogiri.XML(response.raw).xpath("/soap:Envelope", namespaces)
347
+ if not ng.any?
348
+ # Again, this is a shame...
349
+ envstr = '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">'
350
+ extracted = envstr + response.raw.split(envstr)[1].split("</soap:Envelope>")[0] + "</soap:Envelope>"
351
+ ng = Nokogiri.XML(extracted).xpath("/soap:Envelope", namespaces)
352
+ end
353
+ ng
354
+ end
355
+
356
+ # @return [Savon::Operation]
357
+ def _operation(operation_name)
358
+ client.operation(:DixService, :DixServicePort, operation_name)
359
+ end
360
+
361
+ # @return [String]
362
+ def _username
363
+ @username||=ENV["ARGOS_SOAP_USERNAME"]
364
+ end
365
+
366
+ # @return [String]
367
+ def _password
368
+ @password||=ENV["ARGOS_SOAP_PASSWORD"]
369
+ end
370
+
371
+ end
372
+ end