cosmos 3.7.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
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