cosmos 3.7.1 → 3.8.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +1 -0
  3. data/bin/xtce_converter +83 -0
  4. data/cosmos.gemspec +2 -0
  5. data/data/crc.txt +279 -279
  6. data/demo/config/data/crc.txt +171 -171
  7. data/demo/config/targets/INST/cmd_tlm/inst_tlm.txt +1 -1
  8. data/demo/config/targets/INST/lib/sim_inst.rb +17 -1
  9. data/demo/config/targets/INST/screens/hs.txt +4 -1
  10. data/demo/config/targets/INST/screens/limits.txt +13 -13
  11. data/install/config/data/crc.txt +113 -113
  12. data/lib/cosmos/conversions/conversion.rb +6 -0
  13. data/lib/cosmos/conversions/generic_conversion.rb +12 -0
  14. data/lib/cosmos/conversions/new_packet_log_conversion.rb +6 -0
  15. data/lib/cosmos/conversions/polynomial_conversion.rb +6 -0
  16. data/lib/cosmos/conversions/processor_conversion.rb +10 -0
  17. data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +10 -0
  18. data/lib/cosmos/conversions/unix_time_conversion.rb +6 -0
  19. data/lib/cosmos/core_ext/string.rb +18 -5
  20. data/lib/cosmos/gui/line_graph/lines.rb +13 -12
  21. data/lib/cosmos/packets/limits_response.rb +4 -0
  22. data/lib/cosmos/packets/packet.rb +59 -8
  23. data/lib/cosmos/packets/packet_config.rb +679 -6
  24. data/lib/cosmos/packets/packet_item.rb +230 -0
  25. data/lib/cosmos/packets/parsers/packet_parser.rb +30 -22
  26. data/lib/cosmos/processors/new_packet_log_processor.rb +5 -0
  27. data/lib/cosmos/processors/processor.rb +5 -0
  28. data/lib/cosmos/processors/statistics_processor.rb +5 -0
  29. data/lib/cosmos/processors/watermark_processor.rb +5 -0
  30. data/lib/cosmos/script/extract.rb +1 -1
  31. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +7 -2
  32. data/lib/cosmos/tools/tlm_grapher/data_objects/housekeeping_data_object.rb +3 -1
  33. data/lib/cosmos/tools/tlm_grapher/data_objects/xy_data_object.rb +3 -1
  34. data/lib/cosmos/tools/tlm_viewer/widgets/limits_widget.rb +10 -0
  35. data/lib/cosmos/tools/tlm_viewer/widgets/limitsbar_widget.rb +14 -6
  36. data/lib/cosmos/tools/tlm_viewer/widgets/limitscolumn_widget.rb +14 -6
  37. data/lib/cosmos/tools/tlm_viewer/widgets/rangebar_widget.rb +14 -6
  38. data/lib/cosmos/tools/tlm_viewer/widgets/rangecolumn_widget.rb +14 -6
  39. data/lib/cosmos/utilities/simulated_target.rb +1 -0
  40. data/lib/cosmos/version.rb +5 -5
  41. metadata +18 -2
@@ -209,40 +209,40 @@ module Cosmos
209
209
 
210
210
  def add_line(text, y, x = nil, y_labels = nil, x_labels = nil, y_states = nil, x_states = nil, color = 'blue', axis = :LEFT, max_points_plotted = nil)
211
211
  # Validate y data
212
- raise ArgumentError, "GraphView y data was must given in an array-like class" unless y.respond_to?(:[])
213
- raise ArgumentError, "GraphView y data cannot be empty" if y.empty?
214
- raise ArgumentError, "GraphView y data must be Numeric" unless y[0].kind_of?(Numeric)
212
+ raise ArgumentError, "y data must be given in an array-like class" unless y.respond_to?(:[])
213
+ raise ArgumentError, "y data cannot be empty" if y.empty?
214
+ raise ArgumentError, "y data must be Numeric" unless y[0].kind_of?(Numeric)
215
215
 
216
216
  # Validate x data
217
217
  if x.respond_to?(:[])
218
- raise ArgumentError, "GraphView x and y data must be the same size" unless y.length == x.length
219
- raise ArgumentError, "GraphView x data must be Numeric" unless x[0].kind_of?(Numeric)
218
+ raise ArgumentError, "x and y data must be the same size" unless y.length == x.length
219
+ raise ArgumentError, "x data must be Numeric" unless x[0].kind_of?(Numeric)
220
220
  else
221
- raise ArgumentError, "GraphView x data was must given in an array-like class" unless x.nil?
221
+ raise ArgumentError, "x data must be given in an array-like class" unless x.nil?
222
222
  end
223
223
 
224
224
  # Validate y_labels data
225
225
  if y_labels.respond_to?(:[])
226
- raise ArgumentError, "GraphView y_labels and y data must be the same size" unless y.length == y_labels.length
226
+ raise ArgumentError, "y_labels and y data must be the same size" unless y.length == y_labels.length
227
227
  else
228
- raise ArgumentError, "GraphView y_labels data was must given in an array-like class" unless y_labels.nil?
228
+ raise ArgumentError, "y_labels data must be given in an array-like class" unless y_labels.nil?
229
229
  end
230
230
 
231
231
  # Validate x_labels data
232
232
  if x_labels.respond_to?(:[])
233
- raise ArgumentError, "GraphView x_labels and y data must be the same size" unless y.length == x_labels.length
233
+ raise ArgumentError, "x_labels and y data must be the same size" unless y.length == x_labels.length
234
234
  else
235
- raise ArgumentError, "GraphView x_labels data was must given in an array-like class" unless x_labels.nil?
235
+ raise ArgumentError, "x_labels data must be given in an array-like class" unless x_labels.nil?
236
236
  end
237
237
 
238
238
  # Validate y_states data
239
239
  unless y_states.respond_to?(:index)
240
- raise ArgumentError, "GraphView y_states data was must given in an hash-like class" unless y_states.nil?
240
+ raise ArgumentError, "y_states data must be given in an hash-like class" unless y_states.nil?
241
241
  end
242
242
 
243
243
  # Validate x_states data
244
244
  unless x_states.respond_to?(:index)
245
- raise ArgumentError, "GraphView x_states data was must given in an hash-like class" unless x_states.nil?
245
+ raise ArgumentError, "x_states data must be given in an hash-like class" unless x_states.nil?
246
246
  end
247
247
 
248
248
  if max_points_plotted and y.length > max_points_plotted
@@ -278,6 +278,7 @@ module Cosmos
278
278
  unless x
279
279
  x = (1..(y.length)).to_a_to_f
280
280
  end
281
+
281
282
  y_labels = y unless y_labels
282
283
  # x_labels are only set if the formatted time item is used
283
284
  y_states = y_states.clone if y_states
@@ -34,5 +34,9 @@ module Cosmos
34
34
  self.class.to_s.split('::')[-1]
35
35
  end
36
36
 
37
+ def to_config
38
+ " LIMITS_RESPONSE #{self.class.name.class_name_to_filename}\n"
39
+ end
40
+
37
41
  end # class LimitsResponse
38
42
  end
@@ -20,6 +20,8 @@ module Cosmos
20
20
  # as managing PacketItem's limit states.
21
21
  class Packet < Structure
22
22
 
23
+ RESERVED_ITEM_NAMES = ['RECEIVED_TIMESECONDS'.freeze, 'RECEIVED_TIMEFORMATTED'.freeze, 'RECEIVED_COUNT'.freeze]
24
+
23
25
  # @return [String] Name of the target this packet is associated with
24
26
  attr_reader :target_name
25
27
 
@@ -62,6 +64,9 @@ module Cosmos
62
64
  # @return [Boolean] Whether or not messages should be printed for this packet
63
65
  attr_accessor :messages_disabled
64
66
 
67
+ # @return [Boolean] Whether or not this is a 'abstract' packet
68
+ attr_accessor :abstract
69
+
65
70
  # Valid format types
66
71
  VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
67
72
 
@@ -642,6 +647,60 @@ module Cosmos
642
647
  end
643
648
  alias dup clone
644
649
 
650
+ def update_id_items(item)
651
+ if item.id_value
652
+ @id_items ||= []
653
+ @id_items << item
654
+ end
655
+ item
656
+ end
657
+
658
+ def to_config(cmd_or_tlm)
659
+ config = ''
660
+
661
+ if cmd_or_tlm == :TELEMETRY
662
+ config << "TELEMETRY #{self.target_name.to_s.quote_if_necessary} #{self.packet_name.to_s.quote_if_necessary} #{@default_endianness} \"#{self.description}\"\n"
663
+ else
664
+ config << "COMMAND #{self.target_name.to_s.quote_if_necessary} #{self.packet_name.to_s.quote_if_necessary} #{@default_endianness} \"#{self.description}\"\n"
665
+ end
666
+ config << " ALLOW_SHORT\n" if self.short_buffer_allowed
667
+ config << " HAZARDOUS #{self.hazardous_description.to_s.quote_if_necessary}\n" if self.hazardous
668
+ config << " DISABLE_MESSAGES\n" if self.messages_disabled
669
+ if self.disabled
670
+ config << " DISABLED\n"
671
+ elsif self.hidden
672
+ config << " HIDDEN\n"
673
+ end
674
+
675
+ if @processors
676
+ @processors.each do |processor_name, processor|
677
+ config << processor.to_config
678
+ end
679
+ end
680
+
681
+ if @meta
682
+ @meta.each do |key, values|
683
+ config << " META #{key.to_s.quote_if_necessary} #{values.map {|a| a..to_s.quote_if_necessary}.join(" ")}\n"
684
+ end
685
+ end
686
+
687
+ # Items with derived items last
688
+ self.sorted_items.each do |item|
689
+ if item.data_type != :DERIVED
690
+ config << item.to_config(cmd_or_tlm, @default_endianness)
691
+ end
692
+ end
693
+ self.sorted_items.each do |item|
694
+ if item.data_type == :DERIVED
695
+ unless RESERVED_ITEM_NAMES.include?(item.name)
696
+ config << item.to_config(cmd_or_tlm, @default_endianness)
697
+ end
698
+ end
699
+ end
700
+
701
+ config
702
+ end
703
+
645
704
  protected
646
705
 
647
706
  # Performs packet specific processing on the packet. Intended to only be run once for each packet received
@@ -773,14 +832,6 @@ module Cosmos
773
832
  item
774
833
  end
775
834
 
776
- def update_id_items(item)
777
- if item.id_value
778
- @id_items ||= []
779
- @id_items << item
780
- end
781
- item
782
- end
783
-
784
835
  end # class Packet
785
836
 
786
837
  end # module Cosmos
@@ -20,6 +20,8 @@ require 'cosmos/packets/parsers/format_string_parser'
20
20
  require 'cosmos/packets/parsers/processor_parser'
21
21
  require 'cosmos/conversions'
22
22
  require 'cosmos/processors'
23
+ require 'nokogiri'
24
+ require 'ostruct'
23
25
 
24
26
  module Cosmos
25
27
 
@@ -78,11 +80,7 @@ module Cosmos
78
80
  @telemetry['UNKNOWN'] = {}
79
81
  @telemetry['UNKNOWN']['UNKNOWN'] = Packet.new('UNKNOWN', 'UNKNOWN', :BIG_ENDIAN)
80
82
 
81
- # Used during packet processing
82
- @current_cmd_or_tlm = nil
83
- @current_packet = nil
84
- @current_item = nil
85
- @current_limits_group = nil
83
+ reset_processing_variables()
86
84
  end
87
85
 
88
86
  #########################################################################
@@ -95,6 +93,12 @@ module Cosmos
95
93
  # @param filename [String] The name of the configuration file
96
94
  # @param target_name [String] The target name
97
95
  def process_file(filename, process_target_name)
96
+ # Handle .xtce files
97
+ if File.extname(filename).to_s.downcase == ".xtce"
98
+ process_xtce(filename, process_target_name)
99
+ return
100
+ end
101
+
98
102
  # Partial files are included into another file and thus aren't directly processed
99
103
  return if File.basename(filename)[0] == '_' # Partials start with underscore
100
104
 
@@ -203,8 +207,678 @@ module Cosmos
203
207
  finish_packet()
204
208
  end
205
209
 
210
+ # Read in a target definition from a .xtce file
211
+ def process_xtce(filename, override_target_name = nil)
212
+ doc = File.open(filename) { |f| Nokogiri::XML(f, nil, nil, Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NOBLANKS) }
213
+ xtce_process_element(doc.root, 0)
214
+ @current_target_name = override_target_name if override_target_name
215
+ doc.root.children.each do |child|
216
+ xtce_recurse_element(child, 1) do |element, depth|
217
+ xtce_process_element(element, depth)
218
+ end
219
+ end
220
+ finish_packet()
221
+
222
+ # Remove abstract
223
+ @commands[@current_target_name].delete_if {|packet_name, packet| packet.abstract}
224
+ @telemetry[@current_target_name].delete_if {|packet_name, packet| packet.abstract}
225
+
226
+ # Reverse order of packets for the target so ids work correctly
227
+ reverse_packet_order(@current_target_name, @commands)
228
+ reverse_packet_order(@current_target_name, @telemetry)
229
+
230
+ reset_processing_variables()
231
+ end
232
+
233
+ # Convert the PacketConfig back to COSMOS configuration files for each target
234
+ def to_config(output_dir)
235
+ FileUtils.mkdir_p(output_dir)
236
+
237
+ @telemetry.each do |target_name, packets|
238
+ next if target_name == 'UNKNOWN'
239
+ FileUtils.mkdir_p(File.join(output_dir, target_name, 'cmd_tlm'))
240
+ filename = File.join(output_dir, target_name, 'cmd_tlm', target_name.downcase + '_tlm.txt')
241
+ begin
242
+ File.delete(filename)
243
+ rescue
244
+ # Doesn't exist
245
+ end
246
+ packets.each do |packet_name, packet|
247
+ File.open(filename, 'a') do |file|
248
+ file.puts packet.to_config(:TELEMETRY)
249
+ file.puts ""
250
+ end
251
+ end
252
+ end
253
+
254
+ @commands.each do |target_name, packets|
255
+ next if target_name == 'UNKNOWN'
256
+ FileUtils.mkdir_p(File.join(output_dir, target_name, 'cmd_tlm'))
257
+ filename = File.join(output_dir, target_name, 'cmd_tlm', target_name.downcase + '_cmd.txt')
258
+ begin
259
+ File.delete(filename)
260
+ rescue
261
+ # Doesn't exist
262
+ end
263
+ packets.each do |packet_name, packet|
264
+ File.open(filename, 'a') do |file|
265
+ file.puts packet.to_config(:COMMAND)
266
+ file.puts ""
267
+ end
268
+ end
269
+ end
270
+
271
+ # Put limits groups into SYSTEM target
272
+ if @limits_groups.length > 0
273
+ FileUtils.mkdir_p(File.join(output_dir, 'SYSTEM', 'cmd_tlm'))
274
+ filename = File.join(output_dir, 'SYSTEM', 'cmd_tlm', 'limits_groups.txt')
275
+ File.open(filename, 'w') do |file|
276
+ @limits_groups.each do |limits_group_name, limits_group_items|
277
+ file.puts "LIMITS_GROUP #{limits_group_name.to_s.quote_if_necessary}"
278
+ limits_group_items.each do |target_name, packet_name, item_name|
279
+ file.puts " LIMITS_GROUP_ITEM #{target_name.to_s.quote_if_necessary} #{packet_name.to_s.quote_if_necessary} #{item_name.to_s.quote_if_necessary}"
280
+ end
281
+ file.puts ""
282
+ end
283
+ end
284
+ end
285
+
286
+ end # def to_config
287
+
288
+ # Convert the PacketConfig into a .xtce file for each target
289
+ def to_xtce(output_dir)
290
+ FileUtils.mkdir_p(output_dir)
291
+
292
+ # Build target list
293
+ targets = []
294
+ @telemetry.each { |target_name, packets| targets << target_name }
295
+ @commands.each { |target_name, packets| targets << target_name }
296
+ targets.uniq!
297
+
298
+ targets.each do |target_name|
299
+ next if target_name == 'UNKNOWN'
300
+
301
+ # Reverse order of packets for the target so things are expected (reverse) order for xtce
302
+ reverse_packet_order(target_name, @commands)
303
+ reverse_packet_order(target_name, @telemetry)
304
+
305
+ FileUtils.mkdir_p(File.join(output_dir, target_name, 'cmd_tlm'))
306
+ filename = File.join(output_dir, target_name, 'cmd_tlm', target_name.downcase + '.xtce')
307
+ begin
308
+ File.delete(filename)
309
+ rescue
310
+ # Doesn't exist
311
+ end
312
+
313
+ # Gather an make unique all the packet items
314
+ unique_items = {}
315
+ @telemetry[target_name].each do |packet_name, packet|
316
+ packet.sorted_items.each do |item|
317
+ next if item.data_type == :DERIVED
318
+ unique_items[item.name] ||= []
319
+ unique_items[item.name] << item
320
+ end
321
+ end
322
+ unique_items.each do |item_name, items|
323
+ if items.length <= 1
324
+ unique_items[item_name] = items[0]
325
+ next
326
+ end
327
+ # TODO: need to make sure all the items in the array are exactly the same
328
+ unique_items[item_name] = items[0]
329
+ end
330
+
331
+ # Gather and make unique all the command parameters
332
+ unique_arguments = {}
333
+ @commands[target_name].each do |packet_name, packet|
334
+ packet.sorted_items.each do |item|
335
+ next if item.data_type == :DERIVED
336
+ unique_arguments[item.name] ||= []
337
+ unique_arguments[item.name] << item
338
+ end
339
+ end
340
+ unique_arguments.each do |item_name, items|
341
+ if items.length <= 1
342
+ unique_arguments[item_name] = items[0]
343
+ next
344
+ end
345
+ # TODO: need to make sure all the items in the array are exactly the same
346
+ unique_arguments[item_name] = items[0]
347
+ end
348
+
349
+ # Create the xtce file for this target
350
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
351
+ xml['xtce'].SpaceSystem("xmlns:xtce" => "http://www.omg.org/space/xtce",
352
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
353
+ "name" => target_name,
354
+ "xsi:schemaLocation" => "http://www.omg.org/space/xtce http://www.omg.org/spec/XTCE/20061101/06-11-06.xsd") do
355
+ xml['xtce'].TelemetryMetaData do
356
+ xml['xtce'].ParameterTypeSet do
357
+ unique_items.each do |item_name, item|
358
+ item.to_xtce_type('Parameter', xml)
359
+ end
360
+ end
361
+
362
+ xml['xtce'].ParameterSet do
363
+ unique_items.each do |item_name, item|
364
+ item.to_xtce_item('Parameter', xml)
365
+ end
366
+ end
367
+
368
+ xml['xtce'].ContainerSet do
369
+ @telemetry[target_name].each do |packet_name, packet|
370
+ attrs = { :name => (packet_name + '_Base'), :abstract => "true" }
371
+ xml['xtce'].SequenceContainer(attrs) do
372
+ xml['xtce'].EntryList do
373
+ packet.sorted_items.each do |item|
374
+ next if item.data_type == :DERIVED
375
+ # TODO: Handle explicit bit offset in packet and nonunique item names
376
+ xml['xtce'].ParameterRefEntry(:parameterRef => item.name)
377
+ end
378
+ end
379
+ end # Abstract SequenceContainer
380
+
381
+ attrs = { :name => packet_name }
382
+ attrs['shortDescription'] = packet.description if packet.description
383
+ xml['xtce'].SequenceContainer(attrs) do
384
+ xml['xtce'].EntryList
385
+ xml['xtce'].BaseContainer(:containerRef => (packet_name + '_Base')) do
386
+ if packet.id_items and packet.id_items.length > 0
387
+ xml['xtce'].RestrictionCriteria do
388
+ xml['xtce'].ComparisonList do
389
+ packet.id_items.each do |item|
390
+ xml['xtce'].Comparison(:parameterRef => item.name, :value => item.id_value)
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end # Actual SequenceContainer
397
+
398
+ end # @telemetry.each
399
+ end # ContainerSet
400
+ end # TelemetryMetaData
401
+
402
+ xml['xtce'].CommandMetaData do
403
+ xml['xtce'].ArgumentTypeSet do
404
+ unique_arguments.each do |arg_name, arg|
405
+ arg.to_xtce_type('Argument', xml)
406
+ end
407
+ end
408
+ xml['xtce'].MetaCommandSet do
409
+ @commands[target_name].each do |packet_name, packet|
410
+ attrs = { :name => packet_name + "_Base", :abstract => "true" }
411
+ xml['xtce'].MetaCommand(attrs) do
412
+ xml['xtce'].ArgumentList do
413
+ packet.sorted_items.each do |item|
414
+ next if item.data_type == :DERIVED
415
+ item.to_xtce_item('Argument', xml)
416
+ end
417
+ end # ArgumentList
418
+ xml['xtce'].CommandContainer(:name => "#{target_name}_#{packet_name}_CommandContainer") do
419
+ xml['xtce'].EntryList do
420
+ packet.sorted_items.each do |item|
421
+ next if item.data_type == :DERIVED
422
+ xml['xtce'].ArgumentRefEntry(:argumentRef => item.name)
423
+ end
424
+ end
425
+ end
426
+ end # Abstract MetaCommand
427
+
428
+ attrs = { :name => packet_name }
429
+ attrs['shortDescription'] = packet.description if packet.description
430
+ xml['xtce'].MetaCommand(attrs) do
431
+ xml['xtce'].BaseMetaCommand(:metaCommandRef => packet_name + "_Base") do
432
+ if packet.id_items and packet.id_items.length > 0
433
+ xml['xtce'].ArgumentAssignmentList do
434
+ packet.id_items.each do |item|
435
+ xml['xtce'].ArgumentAssignment(:argumentName => item.name, :argumentValue => item.id_value)
436
+ end
437
+ end # ArgumentAssignmentList
438
+ end
439
+ end # BaseMetaCommand
440
+ end # Actual MetaCommand
441
+ end # @commands.each
442
+ end # MetaCommandSet
443
+ end # CommandMetaData
444
+ end # SpaceSystem
445
+ end # builder
446
+ File.open(filename, 'w') do |file|
447
+ file.puts builder.to_xml
448
+ end
449
+ end
450
+ end
451
+
206
452
  protected
207
453
 
454
+ def reset_processing_variables
455
+ # Used during packet processing
456
+ @current_cmd_or_tlm = nil
457
+ @current_packet = nil
458
+ @current_item = nil
459
+ @current_limits_group = nil
460
+
461
+ # Used for xtce processing
462
+ @current_target_name = nil
463
+ @current_type = nil
464
+ @current_meta_command = nil
465
+ @current_parameter = nil
466
+ @current_argument = nil
467
+ @parameter_types = {}
468
+ @argument_types = {}
469
+ @parameters = {}
470
+ @arguments = {}
471
+ @containers = {}
472
+ end
473
+
474
+ def reverse_packet_order(target_name, cmd_or_tlm_hash)
475
+ packets = []
476
+ names_to_remove = []
477
+ cmd_or_tlm_hash[target_name].each do |packet_name, packet|
478
+ packets << packet
479
+ names_to_remove << packet_name
480
+ end
481
+ cmd_or_tlm_hash[target_name].length.times do |i|
482
+ cmd_or_tlm_hash[target_name].delete(names_to_remove[i])
483
+ end
484
+ packets.reverse.each do |packet|
485
+ cmd_or_tlm_hash[target_name][packet.packet_name] = packet
486
+ end
487
+ end
488
+
489
+ XTCE_IGNORED_ELEMENTS = ['text', 'AliasSet', 'Alias', 'Header']
490
+
491
+ def xtce_process_element(element, depth)
492
+ if XTCE_IGNORED_ELEMENTS.include?(element.name)
493
+ return false
494
+ end
495
+ #~ if element.name == 'LongDescription'
496
+ #~ puts "#{' ' * depth}<#{element.name}> #{xtce_format_attributes(element)} #{element.text}"
497
+ #~ else
498
+ #~ puts "#{' ' * depth}<#{element.name}> #{xtce_format_attributes(element)}"
499
+ #~ end
500
+
501
+ case element.name
502
+ when 'SpaceSystem'
503
+ @current_target_name = element["name"].to_s.upcase
504
+
505
+ when 'TelemetryMetaData'
506
+ finish_packet()
507
+ @current_cmd_or_tlm = TELEMETRY
508
+
509
+ when 'CommandMetaData'
510
+ finish_packet()
511
+ @current_cmd_or_tlm = COMMAND
512
+
513
+ when 'ParameterTypeSet', 'EnumerationList', 'ParameterSet', 'ContainerSet', 'EntryList', 'DefaultCalibrator', 'DefaultAlarm', 'RestrictionCriteria', 'ComparisonList', 'MetaCommandSet', 'DefaultCalibrator', 'ArgumentTypeSet', 'ArgumentList', 'ArgumentAssignmentList'
514
+ # Do Nothing
515
+
516
+ when 'EnumeratedParameterType', 'EnumeratedArgumentType', 'IntegerParameterType', 'IntegerArgumentType', 'FloatParameterType', 'FloatArgumentType', 'StringParameterType', 'StringArgumentType', 'BinaryParameterType', 'BinaryArgumentType'
517
+ @current_type = OpenStruct.new
518
+ element.attributes.each do |att_name, att|
519
+ @current_type[att.name] = att.value
520
+ end
521
+ if element.name =~ /Argument/
522
+ @argument_types[element["name"]] = @current_type
523
+ else
524
+ @parameter_types[element["name"]] = @current_type
525
+ end
526
+
527
+ case element.name
528
+ when 'EnumeratedParameterType', 'EnumeratedArgumentType'
529
+ @current_type.xtce_encoding = 'IntegerDataEncoding'
530
+ @current_type.sizeInBits = 8 # This is undocumented but appears to be the design
531
+ when 'IntegerParameterType', 'IntegerArgumentType'
532
+ @current_type.xtce_encoding = 'IntegerDataEncoding'
533
+ @current_type.sizeInBits = 32
534
+ when 'FloatParameterType', 'FloatArgumentType'
535
+ @current_type.xtce_encoding = 'FloatDataEncoding'
536
+ @current_type.sizeInBits = 32
537
+ when 'StringParameterType', 'StringArgumentType'
538
+ @current_type.xtce_encoding = 'StringDataEncoding'
539
+ when 'BinaryParameterType', 'BinaryArgumentType'
540
+ @current_type.xtce_encoding = 'BinaryDataEncoding'
541
+ @current_type.sizeInBits = 8 # This is undocumented but appears to be the design
542
+ end
543
+
544
+ when "SizeInBits"
545
+ xtce_recurse_element(element, depth + 1) do |element, depth|
546
+ if element.name == 'FixedValue'
547
+ @current_type.sizeInBits = Integer(element.text)
548
+ false
549
+ else
550
+ true
551
+ end
552
+ end
553
+ return false # Already recursed
554
+
555
+ when 'UnitSet'
556
+ xtce_recurse_element(element, depth + 1) do |element, depth|
557
+ if element.name == 'Unit'
558
+ @current_type.units ||= ''
559
+ if @current_type.units.empty?
560
+ @current_type.units << element.text.to_s
561
+ else
562
+ @current_type.units << ('/' + element.text.to_s)
563
+ end
564
+ @current_type.units << "^#{element['power']}" if element['power']
565
+ @current_type.units_full ||= ''
566
+ description = element['description'].to_s
567
+ if description.empty?
568
+ @current_type.units_full = @current_type.units
569
+ else
570
+ if @current_type.units_full.empty?
571
+ @current_type.units_full << description
572
+ else
573
+ @current_type.units_full << ('/' + description)
574
+ end
575
+ end
576
+ end
577
+ true
578
+ end
579
+ return false # Already recursed
580
+
581
+ when 'PolynomialCalibrator'
582
+ xtce_recurse_element(element, depth + 1) do |element, depth|
583
+ if element.name == 'Term'
584
+ index = Integer(element['exponent'])
585
+ coeff = Float(element['coefficient'])
586
+ @current_type.conversion ||= PolynomialConversion.new([])
587
+ @current_type.conversion.coeffs[index] = coeff
588
+ @current_type.conversion.coeffs.each_with_index do |value, index|
589
+ @current_type.conversion.coeffs[index] = 0.0 if value.nil?
590
+ end
591
+ end
592
+ true
593
+ end
594
+ return false # Already recursed
595
+
596
+ when 'StaticAlarmRanges'
597
+ xtce_recurse_element(element, depth + 1) do |element, depth|
598
+ if element.name == 'WarningRange'
599
+ @current_type.limits ||= [0.0, 0.0, 0.0, 0.0]
600
+ @current_type.limits[1] = Float(element['minInclusive']) if element['minInclusive']
601
+ @current_type.limits[2] = Float(element['maxInclusive']) if element['maxInclusive']
602
+ elsif element.name == 'CriticalRange'
603
+ @current_type.limits ||= [0.0, 0.0, 0.0, 0.0]
604
+ @current_type.limits[0] = Float(element['minInclusive']) if element['minInclusive']
605
+ @current_type.limits[3] = Float(element['maxInclusive']) if element['maxInclusive']
606
+ end
607
+ true
608
+ end
609
+ return false # Already recursed
610
+
611
+ when "ValidRange"
612
+ @current_type.minInclusive = element['minInclusive']
613
+ @current_type.maxInclusive = element['maxInclusive']
614
+
615
+ when 'Enumeration'
616
+ @current_type.states ||= {}
617
+ @current_type.states[element['label']] = Integer(element['value'])
618
+
619
+ when 'IntegerDataEncoding', 'FloatDataEncoding', 'StringDataEncoding', 'BinaryDataEncoding'
620
+ @current_type.xtce_encoding = element.name
621
+ element.attributes.each do |att_name, att|
622
+ @current_type[att.name] = att.value
623
+ end
624
+ @current_type.sizeInBits = 8 unless element.attributes['sizeInBits']
625
+
626
+ when 'Parameter'
627
+ @current_parameter = OpenStruct.new
628
+ element.attributes.each do |att_name, att|
629
+ @current_parameter[att.name] = att.value
630
+ end
631
+ @parameters[element["name"]] = @current_parameter
632
+
633
+ when 'Argument'
634
+ @current_argument = OpenStruct.new
635
+ element.attributes.each do |att_name, att|
636
+ @current_argument[att.name] = att.value
637
+ end
638
+ @arguments[element["name"]] = @current_argument
639
+
640
+ when 'ParameterProperties'
641
+ element.attributes.each do |att_name, att|
642
+ @current_parameter[att.name] = att.value
643
+ end
644
+
645
+ when "SequenceContainer"
646
+ finish_packet()
647
+ @current_packet = Packet.new(@current_target_name, element['name'], :BIG_ENDIAN, element['shortDescription'])
648
+ @current_packet.abstract = ConfigParser.handle_true_false_nil(element['abstract'])
649
+ @containers[element['name']] = @current_packet
650
+ PacketParser.finish_create_telemetry(@current_packet, @telemetry, @latest_data, @warnings)
651
+
652
+ # Need to check for a BaseContainer now because if we hit it later it will be too late
653
+ xtce_handle_base_container('BaseContainer', element)
654
+
655
+ when 'LongDescription'
656
+ if @current_packet and !@current_packet.description
657
+ @current_packet.description = element.text
658
+ end
659
+
660
+ when 'ParameterRefEntry', 'ArgumentRefEntry'
661
+ if element.name =~ /Argument/
662
+ # Look up the argument and argument type
663
+ argument = @arguments[element['argumentRef']]
664
+ raise "argumentRef #{element['argumentRef']} not found" unless argument
665
+ argument_type = @argument_types[argument.argumentTypeRef]
666
+ raise "argumentTypeRef #{argument.argumentTypeRef} not found" unless argument_type
667
+ refName = 'argumentRef'
668
+ object = argument
669
+ type = argument_type
670
+ else
671
+ # Look up the parameter and parameter type
672
+ parameter = @parameters[element['parameterRef']]
673
+ raise "parameterRef #{element['parameterRef']} not found" unless parameter
674
+ parameter_type = @parameter_types[parameter.parameterTypeRef]
675
+ raise "parameterTypeRef #{parameter.parameterTypeRef} not found" unless parameter_type
676
+ refName = 'parameterRef'
677
+ object = parameter
678
+ type = parameter_type
679
+ end
680
+
681
+ # Add item to packet
682
+ data_type = nil
683
+ case type.xtce_encoding
684
+ when 'IntegerDataEncoding'
685
+ if type.signed == 'false' or type.encoding == 'unsigned'
686
+ data_type = :UINT
687
+ else
688
+ data_type = :INT
689
+ end
690
+ when 'FloatDataEncoding'
691
+ data_type = :FLOAT
692
+ when 'StringDataEncoding'
693
+ data_type = :STRING
694
+ when 'BinaryDataEncoding'
695
+ data_type = :BLOCK
696
+ else
697
+ raise "Referenced Parameter/Argument has no xtce_encoding: #{element[refName]}"
698
+ end
699
+
700
+ item = @current_packet.append_item(object.name, Integer(type.sizeInBits), data_type) #, array_size = nil, endianness = @default_endianness, overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
701
+ item.description = type.shortDescription if type.shortDescription
702
+ if type.states
703
+ item.states = type.states
704
+ end
705
+ if type.units and type.units_full
706
+ item.units = type.units
707
+ item.units_full = type.units_full
708
+ end
709
+ if @current_cmd_or_tlm == COMMAND
710
+ # Possibly add write conversion
711
+ if type.conversion and type.conversion.class == PolynomialConversion
712
+ item.write_conversion = type.conversion
713
+ end
714
+
715
+ # Need to set min, max, and default
716
+ if data_type == :INT
717
+ item.range = (-(2 ** (Integer(type.sizeInBits) - 1)))..((2 ** (Integer(type.sizeInBits) - 1)) - 1)
718
+ if type.minInclusive and type.maxInclusive
719
+ item.range = Integer(type.minInclusive)..Integer(type.maxInclusive)
720
+ end
721
+ item.default = 0
722
+ if item.states and item.states[type.initialValue.to_s.upcase]
723
+ item.default = Integer(item.states[type.initialvalue.to_s.upcase])
724
+ else
725
+ item.default = Integer(type.initialValue) if type.initialValue
726
+ end
727
+ elsif data_type == :UINT
728
+ item.range = 0..((2 ** Integer(type.sizeInBits)) - 1)
729
+ if type.minInclusive and type.maxInclusive
730
+ item.range = Integer(type.minInclusive)..Integer(type.maxInclusive)
731
+ end
732
+ item.default = 0
733
+ if item.states and item.states[type.initialValue.to_s.upcase]
734
+ item.default = Integer(item.states[type.initialValue.to_s.upcase])
735
+ else
736
+ item.default = Integer(type.initialValue) if type.initialValue
737
+ end
738
+ elsif data_type == :FLOAT
739
+ if Integer(type.sizeInBits) == 32
740
+ item.range = -3.402823e38..3.402823e38
741
+ else
742
+ item.range = -Float::MAX..Float::MAX
743
+ end
744
+ if type.minInclusive and type.maxInclusive
745
+ item.range = Float(type.minInclusive)..Float(type.maxInclusive)
746
+ end
747
+ item.default = 0.0
748
+ item.default = Float(type.initialValue) if type.initialValue
749
+ elsif data_type == :STRING
750
+ if type.initialValue
751
+ item.default = type.initialValue
752
+ else
753
+ item.default = ''
754
+ end
755
+ elsif data_type == :BLOCK
756
+ if type.initialValue
757
+ item.default = type.initialValue
758
+ else
759
+ item.default = ''
760
+ end
761
+ end
762
+ else
763
+ # Possibly add read conversion
764
+ if type.conversion and type.conversion.class == PolynomialConversion
765
+ item.read_conversion = type.conversion
766
+ end
767
+
768
+ # Possibly add default limits
769
+ if type.limits
770
+ item.limits.enabled = true
771
+ values = {}
772
+ values[:DEFAULT] = type.limits
773
+ item.limits.values = values
774
+ end
775
+ end
776
+
777
+ when 'BaseContainer'
778
+ # Handled in SequenceContainer/CommandContainer
779
+
780
+ when 'BaseMetaCommand'
781
+ # Handled in MetaCommand
782
+
783
+ when 'Comparison'
784
+ # Need to set ID value for item
785
+ item = @current_packet.get_item(element['parameterRef'])
786
+ item.id_value = Integer(element['value'])
787
+ if @current_cmd_or_tlm == COMMAND
788
+ item.default = item.id_value
789
+ end
790
+ @current_packet.update_id_items(item)
791
+
792
+ when 'MetaCommand'
793
+ finish_packet()
794
+ @current_packet = Packet.new(@current_target_name, element['name'], :BIG_ENDIAN, element['shortDescription'])
795
+ @current_packet.abstract = ConfigParser.handle_true_false_nil(element['abstract'])
796
+ PacketParser.finish_create_command(@current_packet, @commands, @warnings)
797
+
798
+ # Need to check for a BaseContainer now because if we hit it later it will be too late
799
+ xtce_handle_base_container('BaseMetaCommand', element)
800
+
801
+ when 'CommandContainer'
802
+ @containers[element['name']] = @current_packet
803
+
804
+ # Need to check for a BaseContainer now because if we hit it later it will be too late
805
+ xtce_handle_base_container('BaseContainer', element)
806
+
807
+ when 'ArgumentAssignment'
808
+ # Need to set ID value for item
809
+ item = @current_packet.get_item(element['argumentName'])
810
+ value = element['argumentValue']
811
+ if item.states and item.states[value.to_s.upcase]
812
+ item.id_value = item.states[value.to_s.upcase]
813
+ item.default = item.id_value
814
+ else
815
+ item.id_value = Integer(value)
816
+ item.default = item.id_value
817
+ end
818
+ @current_packet.update_id_items(item)
819
+
820
+ else
821
+ puts " Ignoring Unknown: <#{element.name}>"
822
+
823
+ end # case element.name
824
+
825
+ return true # Recurse further
826
+ end
827
+
828
+ def xtce_format_attributes(element)
829
+ string = ''
830
+ element.attributes.each do |att_name, att|
831
+ string << "#{att.name}:#{att.value} "
832
+ end
833
+ if string.length > 0
834
+ string = '( ' + string + ')'
835
+ end
836
+ return string
837
+ end
838
+
839
+ def xtce_recurse_element(element, depth, &block)
840
+ return unless yield(element, depth)
841
+ element.children.each do |child_element|
842
+ xtce_recurse_element(child_element, depth + 1, &block)
843
+ end
844
+ end
845
+
846
+ def xtce_handle_base_container(base_name, element)
847
+ if element.name == base_name
848
+ # Need to add BaseContainer items to current_packet
849
+ # Lookup the base packet
850
+ if base_name == 'BaseMetaCommand'
851
+ base_packet = @commands[@current_packet.target_name][element['metaCommandRef'].to_s.upcase]
852
+ else
853
+ base_packet = @containers[element['containerRef']]
854
+ end
855
+ if base_packet
856
+ count = 0
857
+ base_packet.sorted_items.each do |item|
858
+ unless ['RECEIVED_TIMESECONDS', 'RECEIVED_TIMEFORMATTED', 'RECEIVED_COUNT'].include?(item.name)
859
+ begin
860
+ @current_packet.get_item(item.name)
861
+ rescue
862
+ # Item hasn't already been added so define it
863
+ @current_packet.define(item.clone)
864
+ count += 1
865
+ end
866
+ end
867
+ end
868
+ return
869
+ else
870
+ if base_name == 'BaseMetaCommand'
871
+ raise "Unknown #{base_name}: #{element['metaCommandRef']}"
872
+ else
873
+ raise "Unknown #{base_name}: #{element['containerRef']}"
874
+ end
875
+ end
876
+ end
877
+ element.children.each do |child_element|
878
+ xtce_handle_base_container(base_name, child_element)
879
+ end
880
+ end
881
+
208
882
  def process_current_packet(parser, keyword, params)
209
883
  case keyword
210
884
 
@@ -449,7 +1123,6 @@ module Cosmos
449
1123
  finish_item()
450
1124
  if @current_packet
451
1125
  @warnings += @current_packet.check_bit_offsets
452
-
453
1126
  if @current_cmd_or_tlm == COMMAND
454
1127
  PacketParser.check_item_data_types(@current_packet)
455
1128
  @commands[@current_packet.target_name][@current_packet.packet_name] = @current_packet