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
@@ -13,7 +13,6 @@ require 'cosmos/packets/packet_item_limits'
13
13
  require 'cosmos/conversions/conversion'
14
14
 
15
15
  module Cosmos
16
-
17
16
  # Maintains knowledge of an item in a Packet
18
17
  class PacketItem < StructureItem
19
18
  # @return [String] Printf-style string used to format the item
@@ -305,13 +304,44 @@ module Cosmos
305
304
  hash
306
305
  end
307
306
 
307
+ def calculate_range
308
+ first = range.first
309
+ last = range.last
310
+ if data_type == :FLOAT
311
+ if bit_size == 32
312
+ if range.first == -3.402823e38
313
+ first = 'MIN'
314
+ end
315
+ if range.last == 3.402823e38
316
+ last = 'MAX'
317
+ end
318
+ else
319
+ if range.first == -Float::MAX
320
+ first = 'MIN'
321
+ end
322
+ if range.last == Float::MAX
323
+ last = 'MAX'
324
+ end
325
+ end
326
+ end
327
+ return [first, last]
328
+ end
329
+
308
330
  def to_config(cmd_or_tlm, default_endianness)
309
331
  config = ''
310
332
  if cmd_or_tlm == :TELEMETRY
311
333
  if self.array_size
312
334
  config << " ARRAY_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.array_size} \"#{self.description.to_s.gsub("\"", "'")}\""
313
335
  elsif self.id_value
314
- config << " ID_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.id_value} \"#{self.description.to_s.gsub("\"", "'")}\""
336
+ id_value = self.id_value
337
+ if self.data_type == :BLOCK || self.data_type == :STRING
338
+ unless self.id_value.is_printable?
339
+ id_value = "0x" + self.id_value.simple_formatted
340
+ else
341
+ id_value = "\"#{self.id_value}\""
342
+ end
343
+ end
344
+ config << " ID_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{id_value} \"#{self.description.to_s.gsub("\"", "'")}\""
315
345
  else
316
346
  config << " ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.description.to_s.gsub("\"", "'")}\""
317
347
  end
@@ -320,15 +350,27 @@ module Cosmos
320
350
  config << " ARRAY_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.array_size} \"#{self.description.to_s.gsub("\"", "'")}\""
321
351
  elsif self.id_value
322
352
  if self.data_type == :BLOCK or self.data_type == :STRING
323
- config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.default}\" \"#{self.description.to_s.gsub("\"", "'")}\""
353
+ unless self.id_value.is_printable?
354
+ id_value = "0x" + self.id_value.simple_formatted
355
+ else
356
+ id_value = "\"#{self.id_value}\""
357
+ end
358
+ config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{id_value} \"#{self.description.to_s.gsub("\"", "'")}\""
324
359
  else
325
- config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.range.first} #{self.range.last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
360
+ first, last = calculate_range()
361
+ config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{first} #{last} #{self.id_value} \"#{self.description.to_s.gsub("\"", "'")}\""
326
362
  end
327
363
  else
328
364
  if self.data_type == :BLOCK or self.data_type == :STRING
329
- config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.default}\" \"#{self.description.to_s.gsub("\"", "'")}\""
365
+ unless self.default.is_printable?
366
+ default = "0x" + self.default.simple_formatted
367
+ else
368
+ default = "\"#{self.default}\""
369
+ end
370
+ config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{default} \"#{self.description.to_s.gsub("\"", "'")}\""
330
371
  else
331
- config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.range.first} #{self.range.last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
372
+ first, last = calculate_range()
373
+ config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{first} #{last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
332
374
  end
333
375
  end
334
376
  end
@@ -359,7 +401,12 @@ module Cosmos
359
401
  if self.limits
360
402
  if self.limits.values
361
403
  self.limits.values.each do |limits_set, limits_values|
362
- config << " LIMITS #{limits_set} #{self.limits.persistence_setting} #{self.limits.enabled ? 'ENABLED' : 'DISABLED'} #{limits_values[0]} #{limits_values[1]} #{limits_values[2]} #{limits_values[3]} #{limits_values[4]} #{limits_values[5]}\n"
404
+ config << " LIMITS #{limits_set} #{self.limits.persistence_setting} #{self.limits.enabled ? 'ENABLED' : 'DISABLED'} #{limits_values[0]} #{limits_values[1]} #{limits_values[2]} #{limits_values[3]}"
405
+ if limits_values[4] && limits_values[5]
406
+ config << " #{limits_values[4]} #{limits_values[5]}\n"
407
+ else
408
+ config << "\n"
409
+ end
363
410
  end
364
411
  end
365
412
  config << self.limits.response.to_config if self.limits.response
@@ -374,198 +421,8 @@ module Cosmos
374
421
  config
375
422
  end
376
423
 
377
- def to_xtce_type(param_or_arg, xml)
378
- # TODO: Spline Conversions
379
- case self.data_type
380
- when :INT, :UINT
381
- attrs = { :name => (self.name + '_Type') }
382
- attrs[:initialValue] = self.default if self.default and !self.array_size
383
- attrs[:shortDescription] = self.description if self.description
384
- if @states and self.default and @states.key(self.default)
385
- attrs[:initialValue] = @states.key(self.default) and !self.array_size
386
- end
387
- if self.data_type == :INT
388
- signed = 'true'
389
- encoding = 'twosCompliment'
390
- else
391
- signed = 'false'
392
- encoding = 'unsigned'
393
- end
394
- if @states
395
- xml['xtce'].send('Enumerated' + param_or_arg + 'Type', attrs) do
396
- to_xtce_endianness(xml)
397
- to_xtce_units(xml)
398
- xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding)
399
- xml['xtce'].EnumerationList do
400
- @states.each do |state_name, state_value|
401
- xml['xtce'].Enumeration(:value => state_value, :label => state_name)
402
- end
403
- end
404
- end
405
- else
406
- if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
407
- type_string = 'Float' + param_or_arg + 'Type'
408
- else
409
- type_string = 'Integer' + param_or_arg + 'Type'
410
- attrs[:signed] = signed
411
- end
412
- xml['xtce'].send(type_string, attrs) do
413
- to_xtce_endianness(xml)
414
- to_xtce_units(xml)
415
- if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
416
- xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding) do
417
- to_xtce_conversion(xml)
418
- end
419
- else
420
- xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding)
421
- end
422
- if self.limits
423
- if self.limits.values
424
- self.limits.values.each do |limits_set, limits_values|
425
- if limits_set == :DEFAULT
426
- xml['xtce'].DefaultAlarm do
427
- xml['xtce'].StaticAlarmRanges do
428
- xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
429
- xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
430
- end
431
- end
432
- end
433
- end
434
- end
435
- end
436
- if self.range
437
- xml['xtce'].ValidRange(:minInclusive => self.range.first, :maxInclusive => self.range.last)
438
- end
439
- end # Type
440
- end # if @states
441
- when :FLOAT
442
- attrs = { :name => (self.name + '_Type'), :sizeInBits => self.bit_size }
443
- attrs[:initialValue] = self.default if self.default and !self.array_size
444
- attrs[:shortDescription] = self.description if self.description
445
- xml['xtce'].send('Float' + param_or_arg + 'Type', attrs) do
446
- to_xtce_endianness(xml)
447
- to_xtce_units(xml)
448
- if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
449
- xml['xtce'].FloatDataEncoding(:sizeInBits => self.bit_size, :encoding => 'IEEE754_1985') do
450
- to_xtce_conversion(xml)
451
- end
452
- else
453
- xml['xtce'].FloatDataEncoding(:sizeInBits => self.bit_size, :encoding => 'IEEE754_1985')
454
- end
455
-
456
- if self.limits
457
- if self.limits.values
458
- self.limits.values.each do |limits_set, limits_values|
459
- if limits_set == :DEFAULT
460
- xml['xtce'].DefaultAlarm do
461
- xml['xtce'].StaticAlarmRanges do
462
- xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
463
- xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
464
- end
465
- end
466
- end
467
- end
468
- end
469
- end
470
-
471
- if self.range
472
- xml['xtce'].ValidRange(:minInclusive => self.range.first, :maxInclusive => self.range.last)
473
- end
474
-
475
- end # Type
476
- when :STRING
477
- # TODO: COSMOS Variably sized strings are not supported in XTCE
478
- attrs = { :name => (self.name + '_Type'), :characterWidth => 8 }
479
- attrs[:initialValue] = self.default if self.default and !self.array_size
480
- attrs[:shortDescription] = self.description if self.description
481
- xml['xtce'].send('String' + param_or_arg + 'Type', attrs) do
482
- to_xtce_endianness(xml)
483
- to_xtce_units(xml)
484
- xml['xtce'].StringDataEncoding(:encoding => 'UTF-8') do
485
- xml['xtce'].SizeInBits do
486
- xml['xtce'].Fixed do
487
- xml['xtce'].FixedValue(self.bit_size.to_s)
488
- end
489
- end
490
- end
491
- end
492
- when :BLOCK
493
- # TODO: COSMOS Variably sized blocks are not supported in XTCE
494
- # TODO: Write string to hex method to support initial value
495
- attrs = { :name => (self.name + '_Type') }
496
- attrs[:shortDescription] = self.description if self.description
497
- #attrs[:initialValue] = self.default if self.default and !self.array_size
498
- xml['xtce'].send('Binary' + param_or_arg + 'Type', attrs) do
499
- to_xtce_endianness(xml)
500
- to_xtce_units(xml)
501
- xml['xtce'].BinaryDataEncoding do
502
- xml['xtce'].SizeInBits do
503
- xml['xtce'].FixedValue(self.bit_size.to_s)
504
- end
505
- end
506
- end
507
- when :DERIVED
508
- raise "DERIVED data type not supported in XTCE"
509
- end
510
-
511
- # Handle arrays
512
- if self.array_size
513
- # The above will have created the type for the array entries. Now we create the type for the actual array.
514
- attrs = { :name => (self.name + '_ArrayType') }
515
- attrs[:shortDescription] = self.description if self.description
516
- attrs[:arrayTypeRef] = (self.name + '_Type')
517
- attrs[:numberOfDimensions] = '1' # COSMOS Only supports one-dimensional arrays
518
- xml['xtce'].send('Array' + param_or_arg + 'Type', attrs)
519
- end
520
- end
521
-
522
- def to_xtce_item(param_or_arg, xml)
523
- if self.array_size
524
- xml['xtce'].send(param_or_arg, :name => self.name, "#{param_or_arg.downcase}TypeRef" => self.name + '_ArrayType')
525
- else
526
- xml['xtce'].send(param_or_arg, :name => self.name, "#{param_or_arg.downcase}TypeRef" => self.name + '_Type')
527
- end
528
- end
529
-
530
424
  protected
531
425
 
532
- def to_xtce_units(xml)
533
- if self.units
534
- xml['xtce'].UnitSet do
535
- xml['xtce'].Unit(self.units, :description => self.units_full)
536
- end
537
- else
538
- xml['xtce'].UnitSet
539
- end
540
- end
541
-
542
- def to_xtce_endianness(xml)
543
- if self.endianness == :LITTLE_ENDIAN and self.bit_size > 8
544
- xml['xtce'].ByteOrderList do
545
- (((self.bit_size - 1)/ 8) + 1).times do |byte_significance|
546
- xml['xtce'].Byte(:byteSignificance => byte_significance)
547
- end
548
- end
549
- end
550
- end
551
-
552
- def to_xtce_conversion(xml)
553
- if self.read_conversion
554
- conversion = self.read_conversion
555
- else
556
- conversion = self.write_conversion
557
- end
558
- if conversion and conversion.class == PolynomialConversion
559
- xml['xtce'].DefaultCalibrator do
560
- xml['xtce'].PolynomialCalibrator do
561
- conversion.coeffs.each_with_index do |coeff, index|
562
- xml['xtce'].Term(:coefficient => coeff, :exponent => index)
563
- end
564
- end
565
- end
566
- end
567
- end
568
-
569
426
  # Convert a value into the given data type
570
427
  def convert(value, data_type)
571
428
  case data_type
@@ -579,7 +436,5 @@ module Cosmos
579
436
  rescue
580
437
  raise ArgumentError, "#{@name}: Invalid value: #{value} for data type: #{data_type}"
581
438
  end
582
-
583
- end # class PacketItem
584
-
585
- end # module Cosmos
439
+ end
440
+ end
@@ -0,0 +1,440 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2014 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+
11
+ require 'nokogiri'
12
+ require 'cosmos/packets/parsers/xtce_parser'
13
+
14
+ module Cosmos
15
+ class XtceConverter
16
+ attr_accessor :current_target_name
17
+
18
+ # Output a previously parsed definition file into the XTCE format
19
+ #
20
+ # @param commands [Hash<String=>Packet>] Hash of all the command packets
21
+ # keyed by the packet name.
22
+ # @param telemetry [Hash<String=>Packet>] Hash of all the telemetry packets
23
+ # keyed by the packet name.
24
+ # that were created while parsing the configuration
25
+ # @param output_dir [String] The name of the output directory to generate
26
+ # the XTCE files. A file is generated for each target.
27
+ def self.convert(commands, telemetry, output_dir)
28
+ XtceConverter.new(commands, telemetry, output_dir)
29
+ end
30
+
31
+ private
32
+
33
+ def initialize(commands, telemetry, output_dir)
34
+ FileUtils.mkdir_p(output_dir)
35
+
36
+ # Build target list
37
+ targets = []
38
+ telemetry.each { |target_name, packets| targets << target_name }
39
+ commands.each { |target_name, packets| targets << target_name }
40
+ targets.uniq!
41
+
42
+ targets.each do |target_name|
43
+ next if target_name == 'UNKNOWN'
44
+
45
+ # Reverse order of packets for the target so things are expected (reverse) order for xtce
46
+ XtceParser.reverse_packet_order(target_name, commands)
47
+ XtceParser.reverse_packet_order(target_name, telemetry)
48
+
49
+ FileUtils.mkdir_p(File.join(output_dir, target_name, 'cmd_tlm'))
50
+ filename = File.join(output_dir, target_name, 'cmd_tlm', target_name.downcase + '.xtce')
51
+ begin
52
+ File.delete(filename)
53
+ rescue
54
+ # Doesn't exist
55
+ end
56
+
57
+ # Create the xtce file for this target
58
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
59
+ xml['xtce'].SpaceSystem("xmlns:xtce" => "http://www.omg.org/space/xtce",
60
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
61
+ "name" => target_name,
62
+ "xsi:schemaLocation" => "http://www.omg.org/space/xtce http://www.omg.org/spec/XTCE/20061101/06-11-06.xsd") do
63
+ create_telemetry(xml, telemetry, target_name)
64
+ create_commands(xml, commands, target_name)
65
+ end # SpaceSystem
66
+ end # builder
67
+ File.open(filename, 'w') do |file|
68
+ file.puts builder.to_xml
69
+ end
70
+ end
71
+ end
72
+
73
+ def create_telemetry(xml, telemetry, target_name)
74
+ # Gather and make unique all the packet items
75
+ unique_items = telemetry[target_name] ? get_unique(telemetry[target_name]) : {}
76
+
77
+ xml['xtce'].TelemetryMetaData do
78
+ xml['xtce'].ParameterTypeSet do
79
+ unique_items.each do |item_name, item|
80
+ to_xtce_type(item, 'Parameter', xml)
81
+ end
82
+ end
83
+
84
+ xml['xtce'].ParameterSet do
85
+ unique_items.each do |item_name, item|
86
+ to_xtce_item(item, 'Parameter', xml)
87
+ end
88
+ end
89
+
90
+ if telemetry[target_name]
91
+ xml['xtce'].ContainerSet do
92
+ telemetry[target_name].each do |packet_name, packet|
93
+ attrs = { :name => (packet_name + '_Base'), :abstract => "true" }
94
+ xml['xtce'].SequenceContainer(attrs) do
95
+ process_entry_list(xml, packet, :TELEMETRY)
96
+ end
97
+
98
+ attrs = { :name => packet_name }
99
+ attrs['shortDescription'] = packet.description if packet.description
100
+ xml['xtce'].SequenceContainer(attrs) do
101
+ xml['xtce'].EntryList
102
+ xml['xtce'].BaseContainer(:containerRef => (packet_name + '_Base')) do
103
+ if packet.id_items && packet.id_items.length > 0
104
+ xml['xtce'].RestrictionCriteria do
105
+ xml['xtce'].ComparisonList do
106
+ packet.id_items.each do |item|
107
+ xml['xtce'].Comparison(:parameterRef => item.name, :value => item.id_value)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end # SequenceContainer
114
+ end # telemetry.each
115
+ end # ContainerSet
116
+ end # if telemetry[target_name]
117
+ end # TelemetryMetaData
118
+ end
119
+
120
+ def create_commands(xml, commands, target_name)
121
+ return unless commands[target_name]
122
+
123
+ xml['xtce'].CommandMetaData do
124
+ xml['xtce'].ArgumentTypeSet do
125
+ get_unique(commands[target_name]).each do |arg_name, arg|
126
+ to_xtce_type(arg, 'Argument', xml)
127
+ end
128
+ end
129
+ xml['xtce'].MetaCommandSet do
130
+ commands[target_name].each do |packet_name, packet|
131
+ attrs = { :name => packet_name + "_Base", :abstract => "true" }
132
+ xml['xtce'].MetaCommand(attrs) do
133
+ xml['xtce'].ArgumentList do
134
+ packet.sorted_items.each do |item|
135
+ next if item.data_type == :DERIVED
136
+ to_xtce_item(item, 'Argument', xml)
137
+ end
138
+ end # ArgumentList
139
+ xml['xtce'].CommandContainer(:name => "#{target_name}_#{packet_name}_CommandContainer") do
140
+ process_entry_list(xml, packet, :COMMAND)
141
+ end
142
+ end # Abstract MetaCommand
143
+
144
+ attrs = { :name => packet_name }
145
+ attrs['shortDescription'] = packet.description if packet.description
146
+ xml['xtce'].MetaCommand(attrs) do
147
+ xml['xtce'].BaseMetaCommand(:metaCommandRef => packet_name + "_Base") do
148
+ if packet.id_items && packet.id_items.length > 0
149
+ xml['xtce'].ArgumentAssignmentList do
150
+ packet.id_items.each do |item|
151
+ xml['xtce'].ArgumentAssignment(:argumentName => item.name, :argumentValue => item.id_value)
152
+ end
153
+ end # ArgumentAssignmentList
154
+ end
155
+ end # BaseMetaCommand
156
+ end # Actual MetaCommand
157
+ end # commands.each
158
+ end # MetaCommandSet
159
+ end # CommandMetaData
160
+ end
161
+
162
+ def get_unique(items)
163
+ unique = {}
164
+ items.each do |packet_name, packet|
165
+ packet.sorted_items.each do |item|
166
+ next if item.data_type == :DERIVED
167
+ unique[item.name] ||= []
168
+ unique[item.name] << item
169
+ end
170
+ end
171
+ unique.each do |item_name, items|
172
+ if items.length <= 1
173
+ unique[item_name] = items[0]
174
+ next
175
+ end
176
+ # TODO: need to make sure all the items in the array are exactly the same
177
+ unique[item_name] = items[0]
178
+ end
179
+ unique
180
+ end
181
+
182
+ # This method is almost the same for commands and telemetry except for the
183
+ # XML element name: [Array]ArgumentRefEntry vs [Array]ParameterRefEntry,
184
+ # and XML reference: argumentRef vs parameterRef.
185
+ # Thus we build the name and use send to dynamically dispatch.
186
+ def process_entry_list(xml, packet, cmd_vs_tlm)
187
+ if cmd_vs_tlm == :COMMAND
188
+ type = "Argument"
189
+ else # :TELEMETRY
190
+ type = "Parameter"
191
+ end
192
+ xml['xtce'].EntryList do
193
+ packed = packet.packed?
194
+ packet.sorted_items.each do |item|
195
+ next if item.data_type == :DERIVED
196
+ # TODO: Handle nonunique item names
197
+ if item.array_size
198
+ # Requiring parameterRef for argument arrays appears to be a defect in the schema
199
+ xml['xtce'].send("Array#{type}RefEntry".intern, :parameterRef => item.name) do
200
+ set_fixed_value(xml, item) if !packed
201
+ xml['xtce'].DimensionList do
202
+ xml['xtce'].Dimension do
203
+ xml['xtce'].StartingIndex do
204
+ xml['xtce'].FixedValue(0)
205
+ end
206
+ xml['xtce'].EndingIndex do
207
+ xml['xtce'].FixedValue((item.array_size / item.bit_size) - 1)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ else
213
+ if packed
214
+ xml['xtce'].send("#{type}RefEntry".intern, "#{type.downcase}Ref".intern => item.name)
215
+ else
216
+ xml['xtce'].send("#{type}RefEntry".intern, "#{type.downcase}Ref".intern => item.name) do
217
+ set_fixed_value(xml, item)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ def set_fixed_value(xml, item)
226
+ if item.bit_offset >= 0
227
+ xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerStart') do
228
+ xml['xtce'].FixedValue(item.bit_offset)
229
+ end
230
+ else
231
+ xml['xtce'].LocationInContainerInBits(:referenceLocation => 'containerEnd') do
232
+ xml['xtce'].FixedValue(-item.bit_offset)
233
+ end
234
+ end
235
+ end
236
+
237
+ def to_xtce_type(item, param_or_arg, xml)
238
+ # TODO: Spline Conversions
239
+ case item.data_type
240
+ when :INT, :UINT
241
+ to_xtce_int(item, param_or_arg, xml)
242
+ when :FLOAT
243
+ to_xtce_float(item, param_or_arg, xml)
244
+ when :STRING
245
+ to_xtce_string(item, param_or_arg, xml, 'String')
246
+ when :BLOCK
247
+ to_xtce_string(item, param_or_arg, xml, 'Binary')
248
+ when :DERIVED
249
+ raise "DERIVED data type not supported in XTCE"
250
+ end
251
+
252
+ # Handle arrays
253
+ if item.array_size
254
+ # The above will have created the type for the array entries. Now we create the type for the actual array.
255
+ attrs = { :name => (item.name + '_ArrayType') }
256
+ attrs[:shortDescription] = item.description if item.description
257
+ attrs[:arrayTypeRef] = (item.name + '_Type')
258
+ attrs[:numberOfDimensions] = '1' # COSMOS Only supports one-dimensional arrays
259
+ xml['xtce'].send('Array' + param_or_arg + 'Type', attrs)
260
+ end
261
+ end
262
+
263
+ def to_xtce_int(item, param_or_arg, xml)
264
+ attrs = { :name => (item.name + '_Type') }
265
+ attrs[:initialValue] = item.default if item.default and !item.array_size
266
+ attrs[:shortDescription] = item.description if item.description
267
+ if item.states and item.default and item.states.key(item.default)
268
+ attrs[:initialValue] = item.states.key(item.default) and !item.array_size
269
+ end
270
+ if item.data_type == :INT
271
+ signed = 'true'
272
+ encoding = 'twosCompliment'
273
+ else
274
+ signed = 'false'
275
+ encoding = 'unsigned'
276
+ end
277
+ if item.states
278
+ xml['xtce'].send('Enumerated' + param_or_arg + 'Type', attrs) do
279
+ to_xtce_endianness(item, xml)
280
+ to_xtce_units(item, xml)
281
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
282
+ xml['xtce'].EnumerationList do
283
+ item.states.each do |state_name, state_value|
284
+ xml['xtce'].Enumeration(:value => state_value, :label => state_name)
285
+ end
286
+ end
287
+ end
288
+ else
289
+ if (item.read_conversion and item.read_conversion.class == PolynomialConversion) or (item.write_conversion and item.write_conversion.class == PolynomialConversion)
290
+ type_string = 'Float' + param_or_arg + 'Type'
291
+ else
292
+ type_string = 'Integer' + param_or_arg + 'Type'
293
+ attrs[:signed] = signed
294
+ end
295
+ xml['xtce'].send(type_string, attrs) do
296
+ to_xtce_endianness(item, xml)
297
+ to_xtce_units(item, xml)
298
+ if (item.read_conversion and item.read_conversion.class == PolynomialConversion) or (item.write_conversion and item.write_conversion.class == PolynomialConversion)
299
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding) do
300
+ to_xtce_conversion(item, xml)
301
+ end
302
+ else
303
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
304
+ end
305
+ if item.limits
306
+ if item.limits.values
307
+ item.limits.values.each do |limits_set, limits_values|
308
+ if limits_set == :DEFAULT
309
+ xml['xtce'].DefaultAlarm do
310
+ xml['xtce'].StaticAlarmRanges do
311
+ xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
312
+ xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+ if item.range
320
+ xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
321
+ end
322
+ end # Type
323
+ end # if item.states
324
+ end
325
+
326
+ def to_xtce_float(item, param_or_arg, xml)
327
+ attrs = { :name => (item.name + '_Type'), :sizeInBits => item.bit_size }
328
+ attrs[:initialValue] = item.default if item.default and !item.array_size
329
+ attrs[:shortDescription] = item.description if item.description
330
+ xml['xtce'].send('Float' + param_or_arg + 'Type', attrs) do
331
+ to_xtce_endianness(item, xml)
332
+ to_xtce_units(item, xml)
333
+ if (item.read_conversion and item.read_conversion.class == PolynomialConversion) or (item.write_conversion and item.write_conversion.class == PolynomialConversion)
334
+ xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985') do
335
+ to_xtce_conversion(item, xml)
336
+ end
337
+ else
338
+ xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985')
339
+ end
340
+
341
+ if item.limits
342
+ if item.limits.values
343
+ item.limits.values.each do |limits_set, limits_values|
344
+ if limits_set == :DEFAULT
345
+ xml['xtce'].DefaultAlarm do
346
+ xml['xtce'].StaticAlarmRanges do
347
+ xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
348
+ xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ if item.range
357
+ xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
358
+ end
359
+ end
360
+ end
361
+
362
+ def to_xtce_string(item, param_or_arg, xml, string_or_binary)
363
+ # TODO: COSMOS Variably sized strings are not supported in XTCE
364
+ attrs = { :name => (item.name + '_Type') }
365
+ attrs[:characterWidth] = 8 if string_or_binary == 'String'
366
+ if item.default && !item.array_size
367
+ unless item.default.is_printable?
368
+ attrs[:initialValue] = '0x' + item.default.simple_formatted
369
+ else
370
+ attrs[:initialValue] = item.default.inspect
371
+ end
372
+ end
373
+ attrs[:shortDescription] = item.description if item.description
374
+ xml['xtce'].send(string_or_binary + param_or_arg + 'Type', attrs) do
375
+ to_xtce_endianness(item, xml)
376
+ to_xtce_units(item, xml)
377
+ if string_or_binary == 'String'
378
+ xml['xtce'].StringDataEncoding(:encoding => 'UTF-8') do
379
+ xml['xtce'].SizeInBits do
380
+ xml['xtce'].Fixed do
381
+ xml['xtce'].FixedValue(item.bit_size.to_s)
382
+ end
383
+ end
384
+ end
385
+ else
386
+ xml['xtce'].BinaryDataEncoding do
387
+ xml['xtce'].SizeInBits do
388
+ xml['xtce'].FixedValue(item.bit_size.to_s)
389
+ end
390
+ end
391
+ end
392
+ end
393
+ end
394
+
395
+ def to_xtce_item(item, param_or_arg, xml)
396
+ if item.array_size
397
+ xml['xtce'].send(param_or_arg, :name => item.name, "#{param_or_arg.downcase}TypeRef" => item.name + '_ArrayType')
398
+ else
399
+ xml['xtce'].send(param_or_arg, :name => item.name, "#{param_or_arg.downcase}TypeRef" => item.name + '_Type')
400
+ end
401
+ end
402
+
403
+ def to_xtce_units(item, xml)
404
+ if item.units
405
+ xml['xtce'].UnitSet do
406
+ xml['xtce'].Unit(item.units, :description => item.units_full)
407
+ end
408
+ else
409
+ xml['xtce'].UnitSet
410
+ end
411
+ end
412
+
413
+ def to_xtce_endianness(item, xml)
414
+ if item.endianness == :LITTLE_ENDIAN and item.bit_size > 8
415
+ xml['xtce'].ByteOrderList do
416
+ (((item.bit_size - 1)/ 8) + 1).times do |byte_significance|
417
+ xml['xtce'].Byte(:byteSignificance => byte_significance)
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ def to_xtce_conversion(item, xml)
424
+ if item.read_conversion
425
+ conversion = item.read_conversion
426
+ else
427
+ conversion = item.write_conversion
428
+ end
429
+ if conversion && conversion.class == PolynomialConversion
430
+ xml['xtce'].DefaultCalibrator do
431
+ xml['xtce'].PolynomialCalibrator do
432
+ conversion.coeffs.each_with_index do |coeff, index|
433
+ xml['xtce'].Term(:coefficient => coeff, :exponent => index)
434
+ end
435
+ end
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end