cosmos 4.1.0 → 4.1.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +5 -0
  3. data/appveyor.yml +2 -0
  4. data/autohotkey/tools/replay.ahk +45 -45
  5. data/autohotkey/tools/script_runner.ahk +3 -9
  6. data/cosmos.gemspec +1 -1
  7. data/data/config/interface_modifiers.yaml +23 -0
  8. data/data/config/screen.yaml +1 -1
  9. data/data/crc.txt +20 -18
  10. data/demo/config/targets/INST/cmd_tlm_server.txt +1 -1
  11. data/lib/cosmos/config/config_parser.rb +8 -3
  12. data/lib/cosmos/gui/dialogs/exception_dialog.rb +20 -5
  13. data/lib/cosmos/interfaces/protocols/burst_protocol.rb +13 -3
  14. data/lib/cosmos/interfaces/protocols/crc_protocol.rb +27 -3
  15. data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +4 -2
  16. data/lib/cosmos/interfaces/protocols/length_protocol.rb +4 -2
  17. data/lib/cosmos/interfaces/protocols/override_protocol.rb +2 -2
  18. data/lib/cosmos/interfaces/protocols/preidentified_protocol.rb +3 -2
  19. data/lib/cosmos/interfaces/protocols/protocol.rb +16 -4
  20. data/lib/cosmos/interfaces/protocols/template_protocol.rb +7 -2
  21. data/lib/cosmos/interfaces/protocols/terminated_protocol.rb +4 -2
  22. data/lib/cosmos/packets/packet_config.rb +19 -859
  23. data/lib/cosmos/packets/packet_item.rb +56 -201
  24. data/lib/cosmos/packets/parsers/xtce_converter.rb +440 -0
  25. data/lib/cosmos/packets/parsers/xtce_parser.rb +682 -0
  26. data/lib/cosmos/tools/config_editor/config_editor.rb +143 -5
  27. data/lib/cosmos/tools/tlm_viewer/screen.rb +1 -1
  28. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +5 -3
  29. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +40 -27
  30. data/lib/cosmos/version.rb +4 -4
  31. data/spec/config/config_parser_spec.rb +39 -2
  32. data/spec/install/config/targets/INST/screens/hs.txt +42 -0
  33. data/spec/install/config/targets/INST/target.txt +2 -0
  34. data/spec/interfaces/protocols/burst_protocol_spec.rb +18 -0
  35. data/spec/interfaces/protocols/length_protocol_spec.rb +49 -0
  36. data/spec/interfaces/udp_interface_spec.rb +0 -9
  37. data/spec/packets/packet_config_spec.rb +21 -144
  38. data/spec/packets/packet_item_spec.rb +68 -4
  39. data/spec/packets/parsers/packet_item_parser_spec.rb +12 -0
  40. data/spec/packets/parsers/xtce_parser_spec.rb +398 -0
  41. data/spec/tools/tlm_viewer/tlm_viewer_config_spec.rb +401 -0
  42. metadata +9 -10
@@ -19,9 +19,31 @@ module Cosmos
19
19
  ERROR = "ERROR" # on CRC mismatch
20
20
  DISCONNECT = "DISCONNECT" # on CRC mismatch
21
21
 
22
- def initialize(write_item_name, strip_crc, bad_strategy, bit_offset,
23
- bit_size = 32, endianness = 'BIG_ENDIAN',
24
- poly = nil, seed = nil, xor = nil, reflect = nil)
22
+ # @param write_item_name [String/nil] Item to fill with calculated CRC value for outgoing packets (nil = don't fill)
23
+ # @param strip_crc [Boolean] Whether or not to remove the CRC from incoming packets
24
+ # @param bad_strategy [ERROR/DISCONNECT] How to handle CRC errors on incoming packets. ERROR = Just log the error, DISCONNECT = Disconnect interface
25
+ # @param bit_offset [Integer] Bit offset of the CRC in the data. Can be negative to indicate distance from end of packet
26
+ # @param bit_size [Integer] Bit size of the CRC - Must be 16, 32, or 64
27
+ # @param endianness [BIG_ENDIAN/LITTLE_ENDIAN] Endianness of the CRC
28
+ # @param poly [Integer] Polynomial to use when calculating the CRC
29
+ # @param seed [Integer] Seed value to start the calculation
30
+ # @param xor [Boolean] Whether to XOR the CRC result with 0xFFFF
31
+ # @param reflect [Boolean] Whether to bit reverse each byte of data before calculating the CRC
32
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
33
+ def initialize(
34
+ write_item_name = nil,
35
+ strip_crc = false,
36
+ bad_strategy = "ERROR",
37
+ bit_offset = -32,
38
+ bit_size = 32,
39
+ endianness = 'BIG_ENDIAN',
40
+ poly = nil,
41
+ seed = nil,
42
+ xor = nil,
43
+ reflect = nil,
44
+ allow_empty_data = nil
45
+ )
46
+ super(allow_empty_data)
25
47
  @write_item_name = ConfigParser.handle_nil(write_item_name)
26
48
  @strip_crc = ConfigParser.handle_true_false(strip_crc)
27
49
  raise "Invalid strip CRC of '#{strip_crc}'. Must be TRUE or FALSE." unless !!@strip_crc == @strip_crc
@@ -95,6 +117,8 @@ module Cosmos
95
117
  end
96
118
 
97
119
  def read_data(data)
120
+ return super(data) if (data.length <= 0)
121
+
98
122
  crc = BinaryAccessor.read(@bit_offset, @bit_size, :UINT, data, @endianness)
99
123
  calculated_crc = @crc.calc(data[0...(@bit_offset / 8)])
100
124
  if calculated_crc != crc
@@ -25,15 +25,17 @@ module Cosmos
25
25
  # telemetry (true) or commands (false)
26
26
  # @param fill_fields (see BurstProtocol#initialize)
27
27
  # @param unknown_raise Whether to raise an exception on an unknown packet
28
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
28
29
  def initialize(
29
30
  min_id_size,
30
31
  discard_leading_bytes = 0,
31
32
  sync_pattern = nil,
32
33
  telemetry = true,
33
34
  fill_fields = false,
34
- unknown_raise = false
35
+ unknown_raise = false,
36
+ allow_empty_data = nil
35
37
  )
36
- super(discard_leading_bytes, sync_pattern, fill_fields)
38
+ super(discard_leading_bytes, sync_pattern, fill_fields, allow_empty_data)
37
39
  @min_id_size = Integer(min_id_size)
38
40
  @telemetry = telemetry
39
41
  @unknown_raise = ConfigParser::handle_true_false(unknown_raise)
@@ -33,6 +33,7 @@ module Cosmos
33
33
  # @param max_length [Integer] The maximum allowed value of the length field
34
34
  # @param fill_length_and_sync_pattern [Boolean] Fill the length field and sync
35
35
  # pattern when writing packets
36
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
36
37
  def initialize(
37
38
  length_bit_offset = 0,
38
39
  length_bit_size = 16,
@@ -42,9 +43,10 @@ module Cosmos
42
43
  discard_leading_bytes = 0,
43
44
  sync_pattern = nil,
44
45
  max_length = nil,
45
- fill_length_and_sync_pattern = false
46
+ fill_length_and_sync_pattern = false,
47
+ allow_empty_data = nil
46
48
  )
47
- super(discard_leading_bytes, sync_pattern, fill_length_and_sync_pattern)
49
+ super(discard_leading_bytes, sync_pattern, fill_length_and_sync_pattern, allow_empty_data)
48
50
 
49
51
  # Save length field attributes
50
52
  @length_bit_offset = Integer(length_bit_offset)
@@ -17,8 +17,8 @@ module Cosmos
17
17
  # methods. Clearing the override requires calling normalize_tlm.
18
18
  class OverrideProtocol < Protocol
19
19
 
20
- # @param allow_empty_data [true/false] Whether STOP should be returned on empty data
21
- def initialize(allow_empty_data = false)
20
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
21
+ def initialize(allow_empty_data = nil)
22
22
  super(allow_empty_data)
23
23
  end
24
24
 
@@ -16,8 +16,9 @@ module Cosmos
16
16
 
17
17
  # @param sync_pattern (see BurstProtocol#initialize)
18
18
  # @param max_length [Integer] The maximum allowed value of the length field
19
- def initialize(sync_pattern = nil, max_length = nil)
20
- super(0, sync_pattern)
19
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
20
+ def initialize(sync_pattern = nil, max_length = nil, allow_empty_data = nil)
21
+ super(0, sync_pattern, false, allow_empty_data)
21
22
  @max_length = ConfigParser.handle_nil(max_length)
22
23
  @max_length = Integer(@max_length) if @max_length
23
24
  end
@@ -17,10 +17,12 @@ module Cosmos
17
17
  attr_accessor :interface
18
18
  attr_accessor :allow_empty_data
19
19
 
20
- # @param allow_empty_data [true/false] Whether STOP should be returned on empty data
21
- def initialize(allow_empty_data = false)
20
+ # @param allow_empty_data [true/false/nil] Whether or not this protocol will allow an empty string
21
+ # to be passed down to later Protocols (instead of returning :STOP). Can be true, false, or nil, where
22
+ # nil is interpreted as true unless the Protocol is the last Protocol of the chain.
23
+ def initialize(allow_empty_data = nil)
22
24
  @interface = nil
23
- @allow_empty_data = ConfigParser.handle_true_false(allow_empty_data)
25
+ @allow_empty_data = ConfigParser.handle_true_false_nil(allow_empty_data)
24
26
  reset()
25
27
  end
26
28
 
@@ -37,7 +39,17 @@ module Cosmos
37
39
 
38
40
  # Ensure we have some data in case this is the only protocol
39
41
  def read_data(data)
40
- return :STOP if (data.length <= 0) && !@allow_empty_data
42
+ if (data.length <= 0)
43
+ if @allow_empty_data.nil?
44
+ if @interface and @interface.read_protocols[-1] == self
45
+ # Last read interface in chain with auto @allow_empty_data
46
+ return :STOP
47
+ end
48
+ elsif !@allow_empty_data
49
+ # Don't @allow_empty_data means STOP
50
+ return :STOP
51
+ end
52
+ end
41
53
  data
42
54
  end
43
55
 
@@ -36,6 +36,7 @@ module Cosmos
36
36
  # for a response
37
37
  # @param raise_exceptions [String] Whether to raise exceptions when errors
38
38
  # occur in the protocol like unexpected responses or response timeouts.
39
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
39
40
  def initialize(
40
41
  write_termination_characters,
41
42
  read_termination_characters,
@@ -48,7 +49,8 @@ module Cosmos
48
49
  fill_fields = false,
49
50
  response_timeout = 5.0,
50
51
  response_polling_period = 0.02,
51
- raise_exceptions = false
52
+ raise_exceptions = false,
53
+ allow_empty_data = nil
52
54
  )
53
55
  super(
54
56
  write_termination_characters,
@@ -56,7 +58,8 @@ module Cosmos
56
58
  strip_read_termination,
57
59
  discard_leading_bytes,
58
60
  sync_pattern,
59
- fill_fields)
61
+ fill_fields,
62
+ allow_empty_data)
60
63
  @response_template = nil
61
64
  @response_packet = nil
62
65
  @response_packets = []
@@ -93,6 +96,8 @@ module Cosmos
93
96
  end
94
97
 
95
98
  def read_data(data)
99
+ return super(data) if (data.length <= 0)
100
+
96
101
  # Drop all data until the initial_read_delay is complete.
97
102
  # This gets rid of unused welcome messages,
98
103
  # prompts, and other junk on initial connections
@@ -27,19 +27,21 @@ module Cosmos
27
27
  # @param discard_leading_bytes (see BurstProtocol#initialize)
28
28
  # @param sync_pattern (see BurstProtocol#initialize)
29
29
  # @param fill_fields (see BurstProtocol#initialize)
30
+ # @param allow_empty_data [true/false/nil] See Protocol#initialize
30
31
  def initialize(
31
32
  write_termination_characters,
32
33
  read_termination_characters,
33
34
  strip_read_termination = true,
34
35
  discard_leading_bytes = 0,
35
36
  sync_pattern = nil,
36
- fill_fields = false
37
+ fill_fields = false,
38
+ allow_empty_data = nil
37
39
  )
38
40
  @write_termination_characters = write_termination_characters.hex_to_byte_string
39
41
  @read_termination_characters = read_termination_characters.hex_to_byte_string
40
42
  @strip_read_termination = ConfigParser.handle_true_false(strip_read_termination)
41
43
 
42
- super(discard_leading_bytes, sync_pattern, fill_fields)
44
+ super(discard_leading_bytes, sync_pattern, fill_fields, allow_empty_data)
43
45
  end
44
46
 
45
47
  def write_data(data)
@@ -18,6 +18,8 @@ require 'cosmos/packets/parsers/limits_response_parser'
18
18
  require 'cosmos/packets/parsers/state_parser'
19
19
  require 'cosmos/packets/parsers/format_string_parser'
20
20
  require 'cosmos/packets/parsers/processor_parser'
21
+ require 'cosmos/packets/parsers/xtce_parser'
22
+ require 'cosmos/packets/parsers/xtce_converter'
21
23
  require 'cosmos/conversions'
22
24
  require 'cosmos/processors'
23
25
  require 'nokogiri'
@@ -93,7 +95,7 @@ module Cosmos
93
95
  def process_file(filename, process_target_name)
94
96
  # Handle .xtce files
95
97
  if File.extname(filename).to_s.downcase == ".xtce"
96
- process_xtce(filename, process_target_name)
98
+ XtceParser.process(@commands, @telemetry, @warnings, filename, process_target_name)
97
99
  return
98
100
  end
99
101
 
@@ -205,33 +207,6 @@ module Cosmos
205
207
  finish_packet()
206
208
  end
207
209
 
208
- # Read in a target definition from a .xtce file
209
- def process_xtce(filename, override_target_name = nil)
210
- doc = File.open(filename) { |f| Nokogiri::XML(f, nil, nil, Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NOBLANKS) }
211
- xtce_process_element(doc.root, 0)
212
- @current_target_name = override_target_name if override_target_name
213
- doc.root.children.each do |child|
214
- xtce_recurse_element(child, 1) do |element, depth|
215
- xtce_process_element(element, depth)
216
- end
217
- end
218
- finish_packet()
219
-
220
- # Remove abstract
221
- if @commands[@current_target_name]
222
- @commands[@current_target_name].delete_if {|packet_name, packet| packet.abstract}
223
- end
224
- if @telemetry[@current_target_name]
225
- @telemetry[@current_target_name].delete_if {|packet_name, packet| packet.abstract}
226
- end
227
-
228
- # Reverse order of packets for the target so ids work correctly
229
- reverse_packet_order(@current_target_name, @commands)
230
- reverse_packet_order(@current_target_name, @telemetry)
231
-
232
- reset_processing_variables()
233
- end
234
-
235
210
  # Convert the PacketConfig back to COSMOS configuration files for each target
236
211
  def to_config(output_dir)
237
212
  FileUtils.mkdir_p(output_dir)
@@ -284,832 +259,35 @@ module Cosmos
284
259
  end
285
260
  end
286
261
  end
287
-
288
262
  end # def to_config
289
263
 
290
- # Convert the PacketConfig into a .xtce file for each target
291
264
  def to_xtce(output_dir)
292
- FileUtils.mkdir_p(output_dir)
293
-
294
- # Build target list
295
- targets = []
296
- @telemetry.each { |target_name, packets| targets << target_name }
297
- @commands.each { |target_name, packets| targets << target_name }
298
- targets.uniq!
299
-
300
- targets.each do |target_name|
301
- next if target_name == 'UNKNOWN'
302
-
303
- # Reverse order of packets for the target so things are expected (reverse) order for xtce
304
- reverse_packet_order(target_name, @commands)
305
- reverse_packet_order(target_name, @telemetry)
306
-
307
- FileUtils.mkdir_p(File.join(output_dir, target_name, 'cmd_tlm'))
308
- filename = File.join(output_dir, target_name, 'cmd_tlm', target_name.downcase + '.xtce')
309
- begin
310
- File.delete(filename)
311
- rescue
312
- # Doesn't exist
313
- end
314
-
315
- # Gather an make unique all the packet items
316
- unique_items = {}
317
- @telemetry[target_name].each do |packet_name, packet|
318
- packet.sorted_items.each do |item|
319
- next if item.data_type == :DERIVED
320
- unique_items[item.name] ||= []
321
- unique_items[item.name] << item
322
- end
323
- end
324
- unique_items.each do |item_name, items|
325
- if items.length <= 1
326
- unique_items[item_name] = items[0]
327
- next
328
- end
329
- # TODO: need to make sure all the items in the array are exactly the same
330
- unique_items[item_name] = items[0]
331
- end
332
-
333
- # Gather and make unique all the command parameters
334
- unique_arguments = {}
335
- @commands[target_name].each do |packet_name, packet|
336
- packet.sorted_items.each do |item|
337
- next if item.data_type == :DERIVED
338
- unique_arguments[item.name] ||= []
339
- unique_arguments[item.name] << item
340
- end
341
- end
342
- unique_arguments.each do |item_name, items|
343
- if items.length <= 1
344
- unique_arguments[item_name] = items[0]
345
- next
346
- end
347
- # TODO: need to make sure all the items in the array are exactly the same
348
- unique_arguments[item_name] = items[0]
349
- end
350
-
351
- # Create the xtce file for this target
352
- builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
353
- xml['xtce'].SpaceSystem("xmlns:xtce" => "http://www.omg.org/space/xtce",
354
- "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
355
- "name" => target_name,
356
- "xsi:schemaLocation" => "http://www.omg.org/space/xtce http://www.omg.org/spec/XTCE/20061101/06-11-06.xsd") do
357
- xml['xtce'].TelemetryMetaData do
358
- xml['xtce'].ParameterTypeSet do
359
- unique_items.each do |item_name, item|
360
- item.to_xtce_type('Parameter', xml)
361
- end
362
- end
363
-
364
- xml['xtce'].ParameterSet do
365
- unique_items.each do |item_name, item|
366
- item.to_xtce_item('Parameter', xml)
367
- end
368
- end
265
+ XtceConverter.convert(@commands, @telemetry, output_dir)
266
+ end
369
267
 
370
- xml['xtce'].ContainerSet do
371
- @telemetry[target_name].each do |packet_name, packet|
372
- attrs = { :name => (packet_name + '_Base'), :abstract => "true" }
373
- xml['xtce'].SequenceContainer(attrs) do
374
- xml['xtce'].EntryList do
375
- packed = packet.packed?
376
- packet.sorted_items.each do |item|
377
- next if item.data_type == :DERIVED
378
- # TODO: Handle nonunique item names
379
- if item.array_size
380
- xml['xtce'].ArrayParameterRefEntry(:parameterRef => item.name) do
381
- if !packed
382
- if item.bit_offset >= 0
383
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerStart') do
384
- xml['xtce'].FixedValue(item.bit_offset)
385
- end
386
- else
387
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerEnd') do
388
- xml['xtce'].FixedValue(-item.bit_offset)
389
- end
390
- end
391
- end
392
- xml['xtce'].DimensionList do
393
- xml['xtce'].Dimension do
394
- xml['xtce'].StartingIndex do
395
- xml['xtce'].FixedValue(0)
396
- end
397
- xml['xtce'].EndingIndex do
398
- xml['xtce'].FixedValue((item.array_size / item.bit_size) - 1)
399
- end
400
- end
401
- end
402
- end
403
- else
404
- if packed
405
- xml['xtce'].ParameterRefEntry(:parameterRef => item.name)
406
- else
407
- xml['xtce'].ParameterRefEntry(:parameterRef => item.name) do
408
- if item.bit_offset >= 0
409
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerStart') do
410
- xml['xtce'].FixedValue(item.bit_offset)
411
- end
412
- else
413
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerEnd') do
414
- xml['xtce'].FixedValue(-item.bit_offset)
415
- end
416
- end
417
- end
418
- end
419
- end
420
- end
421
- end
422
- end # Abstract SequenceContainer
423
-
424
- attrs = { :name => packet_name }
425
- attrs['shortDescription'] = packet.description if packet.description
426
- xml['xtce'].SequenceContainer(attrs) do
427
- xml['xtce'].EntryList
428
- xml['xtce'].BaseContainer(:containerRef => (packet_name + '_Base')) do
429
- if packet.id_items and packet.id_items.length > 0
430
- xml['xtce'].RestrictionCriteria do
431
- xml['xtce'].ComparisonList do
432
- packet.id_items.each do |item|
433
- xml['xtce'].Comparison(:parameterRef => item.name, :value => item.id_value)
434
- end
435
- end
436
- end
437
- end
438
- end
439
- end # Actual SequenceContainer
440
-
441
- end # @telemetry.each
442
- end # ContainerSet
443
- end # TelemetryMetaData
444
-
445
- xml['xtce'].CommandMetaData do
446
- xml['xtce'].ArgumentTypeSet do
447
- unique_arguments.each do |arg_name, arg|
448
- arg.to_xtce_type('Argument', xml)
449
- end
450
- end
451
- xml['xtce'].MetaCommandSet do
452
- @commands[target_name].each do |packet_name, packet|
453
- attrs = { :name => packet_name + "_Base", :abstract => "true" }
454
- xml['xtce'].MetaCommand(attrs) do
455
- xml['xtce'].ArgumentList do
456
- packet.sorted_items.each do |item|
457
- next if item.data_type == :DERIVED
458
- item.to_xtce_item('Argument', xml)
459
- end
460
- end # ArgumentList
461
- xml['xtce'].CommandContainer(:name => "#{target_name}_#{packet_name}_CommandContainer") do
462
- xml['xtce'].EntryList do
463
- packed = packet.packed?
464
- packet.sorted_items.each do |item|
465
- next if item.data_type == :DERIVED
466
- if item.array_size
467
- xml['xtce'].ArrayArgumentRefEntry(:parameterRef => item.name) do
468
- if !packed
469
- if item.bit_offset >= 0
470
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerStart') do
471
- xml['xtce'].FixedValue(item.bit_offset)
472
- end
473
- else
474
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerEnd') do
475
- xml['xtce'].FixedValue(-item.bit_offset)
476
- end
477
- end
478
- end
479
- xml['xtce'].DimensionList do
480
- xml['xtce'].Dimension do
481
- xml['xtce'].StartingIndex do
482
- xml['xtce'].FixedValue(0)
483
- end
484
- xml['xtce'].EndingIndex do
485
- xml['xtce'].FixedValue((item.array_size / item.bit_size) - 1)
486
- end
487
- end
488
- end
489
- end
490
- else
491
- if packed
492
- xml['xtce'].ArgumentRefEntry(:argumentRef => item.name)
493
- else
494
- xml['xtce'].ArgumentRefEntry(:argumentRef => item.name) do
495
- if item.bit_offset >= 0
496
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerStart') do
497
- xml['xtce'].FixedValue(item.bit_offset)
498
- end
499
- else
500
- xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerEnd') do
501
- xml['xtce'].FixedValue(-item.bit_offset)
502
- end
503
- end
504
- end
505
- end
506
- end
507
- end
508
- end
509
- end
510
- end # Abstract MetaCommand
511
-
512
- attrs = { :name => packet_name }
513
- attrs['shortDescription'] = packet.description if packet.description
514
- xml['xtce'].MetaCommand(attrs) do
515
- xml['xtce'].BaseMetaCommand(:metaCommandRef => packet_name + "_Base") do
516
- if packet.id_items and packet.id_items.length > 0
517
- xml['xtce'].ArgumentAssignmentList do
518
- packet.id_items.each do |item|
519
- xml['xtce'].ArgumentAssignment(:argumentName => item.name, :argumentValue => item.id_value)
520
- end
521
- end # ArgumentAssignmentList
522
- end
523
- end # BaseMetaCommand
524
- end # Actual MetaCommand
525
- end # @commands.each
526
- end # MetaCommandSet
527
- end # CommandMetaData
528
- end # SpaceSystem
529
- end # builder
530
- File.open(filename, 'w') do |file|
531
- file.puts builder.to_xml
268
+ # Add current packet into hash if it exists
269
+ def finish_packet()
270
+ finish_item()
271
+ if @current_packet
272
+ @warnings += @current_packet.check_bit_offsets
273
+ if @current_cmd_or_tlm == COMMAND
274
+ PacketParser.check_item_data_types(@current_packet)
275
+ @commands[@current_packet.target_name][@current_packet.packet_name] = @current_packet
276
+ else
277
+ @telemetry[@current_packet.target_name][@current_packet.packet_name] = @current_packet
532
278
  end
279
+ @current_packet = nil
280
+ @current_item = nil
533
281
  end
534
282
  end
535
283
 
536
284
  protected
537
285
 
538
286
  def reset_processing_variables
539
- # Used during packet processing
540
287
  @current_cmd_or_tlm = nil
541
288
  @current_packet = nil
542
289
  @current_item = nil
543
290
  @current_limits_group = nil
544
-
545
- # Used for xtce processing
546
- @current_target_name = nil
547
- @current_type = nil
548
- @current_meta_command = nil
549
- @current_parameter = nil
550
- @current_argument = nil
551
- @parameter_types = {}
552
- @argument_types = {}
553
- @parameters = {}
554
- @arguments = {}
555
- @containers = {}
556
- end
557
-
558
- def reverse_packet_order(target_name, cmd_or_tlm_hash)
559
- if cmd_or_tlm_hash[target_name]
560
- packets = []
561
- names_to_remove = []
562
- cmd_or_tlm_hash[target_name].each do |packet_name, packet|
563
- packets << packet
564
- names_to_remove << packet_name
565
- end
566
- cmd_or_tlm_hash[target_name].length.times do |i|
567
- cmd_or_tlm_hash[target_name].delete(names_to_remove[i])
568
- end
569
- packets.reverse.each do |packet|
570
- cmd_or_tlm_hash[target_name][packet.packet_name] = packet
571
- end
572
- end
573
- end
574
-
575
- XTCE_IGNORED_ELEMENTS = ['text', 'AliasSet', 'Alias', 'Header']
576
-
577
- def xtce_process_element(element, depth)
578
- if XTCE_IGNORED_ELEMENTS.include?(element.name)
579
- return false
580
- end
581
-
582
- case element.name
583
- when 'SpaceSystem'
584
- @current_target_name = element["name"].to_s.upcase
585
-
586
- when 'TelemetryMetaData'
587
- finish_packet()
588
- @current_cmd_or_tlm = TELEMETRY
589
-
590
- when 'CommandMetaData'
591
- finish_packet()
592
- @current_cmd_or_tlm = COMMAND
593
-
594
- when 'ParameterTypeSet', 'EnumerationList', 'ParameterSet', 'ContainerSet', 'EntryList', 'DefaultCalibrator', 'DefaultAlarm',
595
- 'RestrictionCriteria', 'ComparisonList', 'MetaCommandSet', 'DefaultCalibrator', 'ArgumentTypeSet', 'ArgumentList', 'ArgumentAssignmentList',
596
- 'LocationInContainerInBits'
597
-
598
- # Do Nothing
599
-
600
- when 'EnumeratedParameterType', 'EnumeratedArgumentType', 'IntegerParameterType', 'IntegerArgumentType', 'FloatParameterType', 'FloatArgumentType',
601
- 'StringParameterType', 'StringArgumentType', 'BinaryParameterType', 'BinaryArgumentType'
602
-
603
- @current_type = OpenStruct.new
604
- @current_type.endianness = :BIG_ENDIAN
605
- element.attributes.each do |att_name, att|
606
- @current_type[att.name] = att.value
607
- end
608
- if element.name =~ /Argument/
609
- @argument_types[element["name"]] = @current_type
610
- else
611
- @parameter_types[element["name"]] = @current_type
612
- end
613
-
614
- case element.name
615
- when 'EnumeratedParameterType', 'EnumeratedArgumentType'
616
- @current_type.xtce_encoding = 'IntegerDataEncoding'
617
- @current_type.sizeInBits = 8 # This is undocumented but appears to be the design
618
- when 'IntegerParameterType', 'IntegerArgumentType'
619
- @current_type.xtce_encoding = 'IntegerDataEncoding'
620
- @current_type.sizeInBits = 32
621
- when 'FloatParameterType', 'FloatArgumentType'
622
- @current_type.xtce_encoding = 'FloatDataEncoding'
623
- @current_type.sizeInBits = 32
624
- when 'StringParameterType', 'StringArgumentType'
625
- @current_type.xtce_encoding = 'StringDataEncoding'
626
- when 'BinaryParameterType', 'BinaryArgumentType'
627
- @current_type.xtce_encoding = 'BinaryDataEncoding'
628
- @current_type.sizeInBits = 8 # This is undocumented but appears to be the design
629
- end
630
-
631
- when 'ArrayParameterType', 'ArrayArgumentType'
632
- @current_type = OpenStruct.new
633
- element.attributes.each do |att_name, att|
634
- @current_type[att.name] = att.value
635
- end
636
- if element.name =~ /Argument/
637
- @argument_types[element["name"]] = @current_type
638
- else
639
- @parameter_types[element["name"]] = @current_type
640
- end
641
-
642
- when 'ByteOrderList'
643
- byte_list = []
644
- xtce_recurse_element(element, depth + 1) do |element, depth|
645
- if element.name == 'Byte'
646
- if element['byteSignificance']
647
- byte_list << element['byteSignificance'].to_i
648
- end
649
- end
650
- true
651
- end
652
- if byte_list[0] == 0
653
- # Little endian will always start with 0 - Its ok if a single byte item is marked little endian
654
- @current_type.endianness = :LITTLE_ENDIAN
655
- end
656
-
657
- # Verify ordering of byte list is supported
658
- if byte_list[0] >= byte_list[-1]
659
- ordered_byte_list = byte_list.reverse
660
- else
661
- ordered_byte_list = byte_list.clone
662
- end
663
- if ordered_byte_list[0] != 0
664
- msg = "Invalid ByteOrderList detected: #{byte_list.join(", ")}"
665
- Logger.instance.warn msg
666
- @warnings << msg
667
- else
668
- previous_byte = nil
669
- ordered_byte_list.each do |byte|
670
- if previous_byte
671
- if byte - previous_byte != 1
672
- msg = "Invalid ByteOrderList detected: #{byte_list.join(", ")}"
673
- Logger.instance.warn msg
674
- @warnings << msg
675
- break
676
- end
677
- end
678
- previous_byte = byte
679
- end
680
- end
681
-
682
- return false # Already recursed
683
-
684
- when "SizeInBits"
685
- xtce_recurse_element(element, depth + 1) do |element, depth|
686
- if element.name == 'FixedValue'
687
- @current_type.sizeInBits = Integer(element.text)
688
- false
689
- else
690
- true
691
- end
692
- end
693
- return false # Already recursed
694
-
695
- when 'UnitSet'
696
- xtce_recurse_element(element, depth + 1) do |element, depth|
697
- if element.name == 'Unit'
698
- @current_type.units ||= ''
699
- if @current_type.units.empty?
700
- @current_type.units << element.text.to_s
701
- else
702
- @current_type.units << ('/' + element.text.to_s)
703
- end
704
- @current_type.units << "^#{element['power']}" if element['power']
705
- @current_type.units_full ||= ''
706
- description = element['description'].to_s
707
- if description.empty?
708
- @current_type.units_full = @current_type.units
709
- else
710
- if @current_type.units_full.empty?
711
- @current_type.units_full << description
712
- else
713
- @current_type.units_full << ('/' + description)
714
- end
715
- end
716
- end
717
- true
718
- end
719
- return false # Already recursed
720
-
721
- when 'PolynomialCalibrator'
722
- xtce_recurse_element(element, depth + 1) do |element, depth|
723
- if element.name == 'Term'
724
- index = Float(element['exponent']).to_i
725
- coeff = Float(element['coefficient'])
726
- @current_type.conversion ||= PolynomialConversion.new([])
727
- @current_type.conversion.coeffs[index] = coeff
728
- @current_type.conversion.coeffs.each_with_index do |value, index|
729
- @current_type.conversion.coeffs[index] = 0.0 if value.nil?
730
- end
731
- end
732
- true
733
- end
734
- return false # Already recursed
735
-
736
- when 'StaticAlarmRanges'
737
- xtce_recurse_element(element, depth + 1) do |element, depth|
738
- if element.name == 'WarningRange'
739
- @current_type.limits ||= [0.0, 0.0, 0.0, 0.0]
740
- @current_type.limits[1] = Float(element['minInclusive']) if element['minInclusive']
741
- @current_type.limits[2] = Float(element['maxInclusive']) if element['maxInclusive']
742
- elsif element.name == 'CriticalRange'
743
- @current_type.limits ||= [0.0, 0.0, 0.0, 0.0]
744
- @current_type.limits[0] = Float(element['minInclusive']) if element['minInclusive']
745
- @current_type.limits[3] = Float(element['maxInclusive']) if element['maxInclusive']
746
- end
747
- true
748
- end
749
- return false # Already recursed
750
-
751
- when "ValidRange"
752
- @current_type.minInclusive = element['minInclusive']
753
- @current_type.maxInclusive = element['maxInclusive']
754
-
755
- when 'Enumeration'
756
- @current_type.states ||= {}
757
- @current_type.states[element['label']] = Integer(element['value'])
758
-
759
- when 'IntegerDataEncoding', 'FloatDataEncoding', 'StringDataEncoding', 'BinaryDataEncoding'
760
- @current_type.xtce_encoding = element.name
761
- element.attributes.each do |att_name, att|
762
- @current_type[att.name] = att.value
763
- end
764
- @current_type.sizeInBits = 8 unless element.attributes['sizeInBits']
765
-
766
- when 'Parameter'
767
- @current_parameter = OpenStruct.new
768
- element.attributes.each do |att_name, att|
769
- @current_parameter[att.name] = att.value
770
- end
771
- @parameters[element["name"]] = @current_parameter
772
-
773
- when 'Argument'
774
- @current_argument = OpenStruct.new
775
- element.attributes.each do |att_name, att|
776
- @current_argument[att.name] = att.value
777
- end
778
- @arguments[element["name"]] = @current_argument
779
-
780
- when 'ParameterProperties'
781
- element.attributes.each do |att_name, att|
782
- @current_parameter[att.name] = att.value
783
- end
784
-
785
- when "SequenceContainer"
786
- finish_packet()
787
- @current_packet = Packet.new(@current_target_name, element['name'], :BIG_ENDIAN, element['shortDescription'])
788
- @current_packet.abstract = ConfigParser.handle_true_false_nil(element['abstract'])
789
- @containers[element['name']] = @current_packet
790
- PacketParser.finish_create_telemetry(@current_packet, @telemetry, @latest_data, @warnings)
791
-
792
- # Need to check for a BaseContainer now because if we hit it later it will be too late
793
- xtce_handle_base_container('BaseContainer', element)
794
-
795
- when 'LongDescription'
796
- if @current_packet and !@current_packet.description
797
- @current_packet.description = element.text
798
- end
799
-
800
- when 'ParameterRefEntry', 'ArgumentRefEntry', 'ArrayParameterRefEntry', 'ArrayArgumentRefEntry'
801
- reference_location, bit_offset = xtce_handle_location_in_container_in_bits(element)
802
-
803
- array_type = nil
804
- array_bit_size = nil
805
- if element.name =~ /Parameter/
806
- # Look up the parameter and parameter type
807
- parameter = @parameters[element['parameterRef']]
808
- raise "parameterRef #{element['parameterRef']} not found" unless parameter
809
- parameter_type = @parameter_types[parameter.parameterTypeRef]
810
- raise "parameterTypeRef #{parameter.parameterTypeRef} not found" unless parameter_type
811
- if element.name == 'ArrayParameterRefEntry'
812
- array_type = parameter_type
813
- parameter_type = @parameter_types[array_type.arrayTypeRef]
814
- raise "arrayTypeRef #{parameter.arrayTypeRef} not found" unless parameter_type
815
- end
816
- refName = 'parameterRef'
817
- object = parameter
818
- type = parameter_type
819
- else
820
- # Look up the argument and argument type
821
- if element.name == 'ArrayArgumentRefEntry'
822
- # Requiring parameterRef for argument arrays appears to be a defect in the schema
823
- argument = @arguments[element['parameterRef']]
824
- raise "parameterRef #{element['parameterRef']} not found" unless argument
825
- argument_type = @argument_types[argument.argumentTypeRef]
826
- raise "argumentTypeRef #{argument.argumentTypeRef} not found" unless argument_type
827
- array_type = argument_type
828
- argument_type = @argument_types[array_type.arrayTypeRef]
829
- raise "arrayTypeRef #{array_type.arrayTypeRef} not found" unless argument_type
830
- refName = 'parameterRef'
831
- else
832
- argument = @arguments[element['argumentRef']]
833
- raise "argumentRef #{element['argumentRef']} not found" unless argument
834
- argument_type = @argument_types[argument.argumentTypeRef]
835
- raise "argumentTypeRef #{argument.argumentTypeRef} not found" unless argument_type
836
- refName = 'argumentRef'
837
- end
838
- object = argument
839
- type = argument_type
840
- end
841
-
842
- bit_size = Integer(type.sizeInBits)
843
-
844
- if array_type
845
- array_num_items = 1
846
- # Need to determine dimensions
847
- xtce_recurse_element(element, depth + 1) do |element, depth|
848
- if element.name == 'Dimension'
849
- starting_index = 0
850
- ending_index = 0
851
- element.children.each do |child_element|
852
- if child_element.name == 'StartingIndex'
853
- child_element.children.each do |child_element2|
854
- if child_element2.name == 'FixedValue'
855
- starting_index = child_element2.text.to_i
856
- end
857
- end
858
- elsif child_element.name == 'EndingIndex'
859
- child_element.children.each do |child_element2|
860
- if child_element2.name == 'FixedValue'
861
- ending_index = child_element2.text.to_i
862
- end
863
- end
864
- array_num_items *= ((ending_index - starting_index).abs + 1)
865
- end
866
- false # Don't recurse again
867
- end
868
- false # Don't recurse again
869
- else
870
- true # Keep recursing
871
- end
872
- end
873
- array_bit_size = array_num_items * bit_size
874
- end
875
-
876
- # Add item to packet
877
- data_type = nil
878
- case type.xtce_encoding
879
- when 'IntegerDataEncoding'
880
- if type.signed == 'false' or type.encoding == 'unsigned'
881
- data_type = :UINT
882
- else
883
- data_type = :INT
884
- end
885
- when 'FloatDataEncoding'
886
- data_type = :FLOAT
887
- when 'StringDataEncoding'
888
- data_type = :STRING
889
- when 'BinaryDataEncoding'
890
- data_type = :BLOCK
891
- else
892
- raise "Referenced Parameter/Argument has no xtce_encoding: #{element[refName]}"
893
- end
894
-
895
- if bit_offset
896
- case reference_location
897
- when 'containerStart'
898
- item = @current_packet.define_item(object.name, bit_offset, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
899
- when 'containerEnd'
900
- item = @current_packet.define_item(object.name, -bit_offset, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
901
- when 'previousEntry', nil
902
- item = @current_packet.define_item(object.name, @current_packet.length + bit_offset, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
903
- when 'nextEntry'
904
- raise 'nextEntry is not supported'
905
- end
906
- else
907
- item = @current_packet.append_item(object.name, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
908
- end
909
-
910
- item.description = type.shortDescription if type.shortDescription
911
- if type.states
912
- item.states = type.states
913
- end
914
- if type.units and type.units_full
915
- item.units = type.units
916
- item.units_full = type.units_full
917
- end
918
- if @current_cmd_or_tlm == COMMAND
919
- # Possibly add write conversion
920
- if type.conversion and type.conversion.class == PolynomialConversion
921
- item.write_conversion = type.conversion
922
- end
923
-
924
- # Need to set min, max, and default
925
- if data_type == :INT or data_type == :UINT
926
- if data_type == :INT
927
- item.range = (-(2 ** (Integer(type.sizeInBits) - 1)))..((2 ** (Integer(type.sizeInBits) - 1)) - 1)
928
- else
929
- item.range = 0..((2 ** Integer(type.sizeInBits)) - 1)
930
- end
931
- if type.minInclusive and type.maxInclusive
932
- item.range = Integer(type.minInclusive)..Integer(type.maxInclusive)
933
- end
934
- if item.array_size
935
- item.default = []
936
- else
937
- item.default = 0
938
- if item.states and item.states[type.initialValue.to_s.upcase]
939
- item.default = Integer(item.states[type.initialValue.to_s.upcase])
940
- else
941
- item.default = Integer(type.initialValue) if type.initialValue
942
- end
943
- end
944
- elsif data_type == :FLOAT
945
- if Integer(type.sizeInBits) == 32
946
- item.range = -3.402823e38..3.402823e38
947
- else
948
- item.range = -Float::MAX..Float::MAX
949
- end
950
- if type.minInclusive and type.maxInclusive
951
- item.range = Float(type.minInclusive)..Float(type.maxInclusive)
952
- end
953
- if item.array_size
954
- item.default = []
955
- else
956
- item.default = 0.0
957
- item.default = Float(type.initialValue) if type.initialValue
958
- end
959
- elsif data_type == :STRING
960
- if item.array_size
961
- item.default = []
962
- else
963
- if type.initialValue
964
- item.default = type.initialValue
965
- else
966
- item.default = ''
967
- end
968
- end
969
- elsif data_type == :BLOCK
970
- if item.array_size
971
- item.default = []
972
- else
973
- if type.initialValue
974
- item.default = type.initialValue
975
- else
976
- item.default = ''
977
- end
978
- end
979
- end
980
- else
981
- # Possibly add read conversion
982
- if type.conversion and type.conversion.class == PolynomialConversion
983
- item.read_conversion = type.conversion
984
- end
985
-
986
- # Possibly add default limits
987
- if type.limits
988
- item.limits.enabled = true
989
- values = {}
990
- values[:DEFAULT] = type.limits
991
- item.limits.values = values
992
- end
993
- end
994
-
995
- return false # Already recursed
996
-
997
- when 'BaseContainer'
998
- # Handled in SequenceContainer/CommandContainer
999
-
1000
- when 'BaseMetaCommand'
1001
- # Handled in MetaCommand
1002
-
1003
- when 'Comparison'
1004
- # Need to set ID value for item
1005
- item = @current_packet.get_item(element['parameterRef'])
1006
- item.id_value = Integer(element['value'])
1007
- if @current_cmd_or_tlm == COMMAND
1008
- item.default = item.id_value
1009
- end
1010
- @current_packet.update_id_items(item)
1011
-
1012
- when 'MetaCommand'
1013
- finish_packet()
1014
- @current_packet = Packet.new(@current_target_name, element['name'], :BIG_ENDIAN, element['shortDescription'])
1015
- @current_packet.abstract = ConfigParser.handle_true_false_nil(element['abstract'])
1016
- PacketParser.finish_create_command(@current_packet, @commands, @warnings)
1017
-
1018
- # Need to check for a BaseContainer now because if we hit it later it will be too late
1019
- xtce_handle_base_container('BaseMetaCommand', element)
1020
-
1021
- when 'CommandContainer'
1022
- @containers[element['name']] = @current_packet
1023
-
1024
- # Need to check for a BaseContainer now because if we hit it later it will be too late
1025
- xtce_handle_base_container('BaseContainer', element)
1026
-
1027
- when 'ArgumentAssignment'
1028
- # Need to set ID value for item
1029
- item = @current_packet.get_item(element['argumentName'])
1030
- value = element['argumentValue']
1031
- if item.states and item.states[value.to_s.upcase]
1032
- item.id_value = item.states[value.to_s.upcase]
1033
- item.default = item.id_value
1034
- else
1035
- item.id_value = Integer(value)
1036
- item.default = item.id_value
1037
- end
1038
- @current_packet.update_id_items(item)
1039
-
1040
- else
1041
- puts " Ignoring Unknown: <#{element.name}>"
1042
-
1043
- end # case element.name
1044
-
1045
- return true # Recurse further
1046
- end
1047
-
1048
- def xtce_format_attributes(element)
1049
- string = ''
1050
- element.attributes.each do |att_name, att|
1051
- string << "#{att.name}:#{att.value} "
1052
- end
1053
- if string.length > 0
1054
- string = '( ' + string + ')'
1055
- end
1056
- return string
1057
- end
1058
-
1059
- def xtce_recurse_element(element, depth, &block)
1060
- return unless yield(element, depth)
1061
- element.children.each do |child_element|
1062
- xtce_recurse_element(child_element, depth + 1, &block)
1063
- end
1064
- end
1065
-
1066
- def xtce_handle_base_container(base_name, element)
1067
- if element.name == base_name
1068
- # Need to add BaseContainer items to current_packet
1069
- # Lookup the base packet
1070
- if base_name == 'BaseMetaCommand'
1071
- base_packet = @commands[@current_packet.target_name][element['metaCommandRef'].to_s.upcase]
1072
- else
1073
- base_packet = @containers[element['containerRef']]
1074
- end
1075
- if base_packet
1076
- count = 0
1077
- base_packet.sorted_items.each do |item|
1078
- unless ['RECEIVED_TIMESECONDS', 'RECEIVED_TIMEFORMATTED', 'RECEIVED_COUNT'].include?(item.name)
1079
- begin
1080
- @current_packet.get_item(item.name)
1081
- rescue
1082
- # Item hasn't already been added so define it
1083
- @current_packet.define(item.clone)
1084
- count += 1
1085
- end
1086
- end
1087
- end
1088
- return
1089
- else
1090
- if base_name == 'BaseMetaCommand'
1091
- raise "Unknown #{base_name}: #{element['metaCommandRef']}"
1092
- else
1093
- raise "Unknown #{base_name}: #{element['containerRef']}"
1094
- end
1095
- end
1096
- end
1097
- element.children.each do |child_element|
1098
- xtce_handle_base_container(base_name, child_element)
1099
- end
1100
- end
1101
-
1102
- def xtce_handle_location_in_container_in_bits(element)
1103
- element.children.each do |child_element|
1104
- if child_element.name == 'LocationInContainerInBits'
1105
- child_element.children.each do |child_element2|
1106
- if child_element2.name == 'FixedValue'
1107
- return [child_element['referenceLocation'], Integer(child_element2.text)]
1108
- end
1109
- end
1110
- end
1111
- end
1112
- return [nil, nil]
1113
291
  end
1114
292
 
1115
293
  def process_current_packet(parser, keyword, params)
@@ -1352,22 +530,6 @@ module Cosmos
1352
530
  end
1353
531
  end
1354
532
 
1355
- # Add current packet into hash if it exists
1356
- def finish_packet
1357
- finish_item()
1358
- if @current_packet
1359
- @warnings += @current_packet.check_bit_offsets
1360
- if @current_cmd_or_tlm == COMMAND
1361
- PacketParser.check_item_data_types(@current_packet)
1362
- @commands[@current_packet.target_name][@current_packet.packet_name] = @current_packet
1363
- else
1364
- @telemetry[@current_packet.target_name][@current_packet.packet_name] = @current_packet
1365
- end
1366
- @current_packet = nil
1367
- @current_item = nil
1368
- end
1369
- end
1370
-
1371
533
  def start_item(parser)
1372
534
  finish_item()
1373
535
  @current_item = PacketItemParser.parse(parser, @current_packet, @current_cmd_or_tlm)
@@ -1387,7 +549,5 @@ module Cosmos
1387
549
  @current_item = nil
1388
550
  end
1389
551
  end
1390
-
1391
- end # class PacketConfig
1392
-
1393
- end # module Cosmos
552
+ end
553
+ end