openc3 6.10.2 → 6.10.3

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.
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -24,6 +24,16 @@ require 'nokogiri'
24
24
  require 'openc3/packets/parsers/xtce_parser'
25
25
  require 'fileutils'
26
26
 
27
+ DYNAMIC_STRING_LEN = 2048
28
+ INVALID_CHARS = '[]./'
29
+ REPLACEMENT_CHAR = '_'
30
+ ALIAS_NAMESPACE = 'COSMOS'
31
+
32
+ COMBINED_NAME = "COMBINED"
33
+ MAX_64_BIT_INT = 18446744073709551614
34
+ COSMOS_NATIVE_DERIVED_ITEMS = ['PACKET_TIMESECONDS', 'PACKET_TIMEFORMATTED', 'RECEIVED_TIMESECONDS', 'RECEIVED_TIMEFORMATTED', 'RECEIVED_COUNT']
35
+ PACKET_TIME_STRING = "PACKET_TIME"
36
+
27
37
  module OpenC3
28
38
  class XtceConverter
29
39
  attr_accessor :current_target_name
@@ -37,13 +47,74 @@ module OpenC3
37
47
  # that were created while parsing the configuration
38
48
  # @param output_dir [String] The name of the output directory to generate
39
49
  # the XTCE files. A file is generated for each target.
40
- def self.convert(commands, telemetry, output_dir)
41
- XtceConverter.new(commands, telemetry, output_dir)
50
+ def self.convert(commands, telemetry, output_dir, time_association_name)
51
+ XtceConverter.new(commands, telemetry, output_dir, time_association_name)
52
+ end
53
+
54
+ def self.combine_output_xtce(output_dir, root_target_name = nil)
55
+ combined_file_directory = File.join(output_dir, 'TARGETS_COMBINED', 'cmd_tlm')
56
+ begin
57
+ FileUtils.rm_rf(combined_file_directory)
58
+ rescue
59
+ # doesn't exist
60
+ end
61
+ file_pattern = File.join(output_dir, "**", "*.xtce")
62
+ xml_files = Dir.glob(file_pattern)
63
+ if xml_files.empty?
64
+ puts "No *.xtce files found to combine."
65
+ elsif xml_files.length == 1
66
+ puts "Only one *.xtce file found. No need to unify."
67
+ else
68
+ puts "Multiple targets found. Creating Unified XTCE representation."
69
+ FileUtils.mkdir_p(combined_file_directory)
70
+ file_basename = "combined"
71
+ xml_files.each do |file_path|
72
+ file_basename += "_#{File.basename(file_path, ".*")}"
73
+ end
74
+ full_file_name = File.join(combined_file_directory, file_basename.downcase + '.xtce')
75
+ begin
76
+ File.delete(full_file_name)
77
+ rescue
78
+ # Doesn't exist
79
+ end
80
+ xml_files.each do |file_path|
81
+ file_basename += File.basename(file_path, ".*")
82
+ end
83
+ root_builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
84
+ xml['xtce'].SpaceSystem("xmlns:xtce" => "http://www.omg.org/spec/XTCE/20180204",
85
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
86
+ "name" => root_target_name ? root_target_name : "root",
87
+ "xsi:schemaLocation" => "http://www.omg.org/spec/XTCE/20180204 https://www.omg.org/spec/XTCE/20180204/SpaceSystem.xsd")
88
+ end
89
+ new_doc = root_builder.doc
90
+ new_root = new_doc.root
91
+ xml_files.each do |file_path|
92
+ source_doc = Nokogiri::XML(File.open(file_path))
93
+ target_root = source_doc.root
94
+ target_root.attributes.each do |name, attr|
95
+ unless name == "name"
96
+ attr.remove
97
+ end
98
+ end
99
+ if root_target_name == target_root["name"]
100
+ nodes_to_add_reversed = target_root.children.to_a.reverse
101
+ nodes_to_add_reversed.each do |child_node|
102
+ new_root.prepend_child(child_node)
103
+ end
104
+ else
105
+ new_root.add_child(target_root)
106
+ end
107
+ end
108
+ File.open(full_file_name, 'w') do |file|
109
+ file.puts new_doc.to_xml
110
+ end
111
+ full_file_name
112
+ end
42
113
  end
43
114
 
44
115
  private
45
116
 
46
- def initialize(commands, telemetry, output_dir)
117
+ def initialize(commands, telemetry, output_dir, time_association_name)
47
118
  FileUtils.mkdir_p(output_dir)
48
119
 
49
120
  # Build target list
@@ -52,6 +123,8 @@ module OpenC3
52
123
  commands.each { |target_name, packets| targets << target_name }
53
124
  targets.uniq!
54
125
 
126
+ @packet_time_string = time_association_name
127
+
55
128
  targets.each do |target_name|
56
129
  next if target_name == 'UNKNOWN'
57
130
 
@@ -69,12 +142,19 @@ module OpenC3
69
142
 
70
143
  # Create the xtce file for this target
71
144
  builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
72
- xml['xtce'].SpaceSystem("xmlns:xtce" => "http://www.omg.org/space/xtce",
145
+ xml['xtce'].SpaceSystem("xmlns:xtce" => "http://www.omg.org/spec/XTCE/20180204",
73
146
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
74
147
  "name" => target_name,
75
- "xsi:schemaLocation" => "http://www.omg.org/space/xtce http://www.omg.org/spec/XTCE/20061101/06-11-06.xsd") do
148
+ "xsi:schemaLocation" => "http://www.omg.org/spec/XTCE/20180204 https://www.omg.org/spec/XTCE/20180204/SpaceSystem.xsd") do
76
149
  create_telemetry(xml, telemetry, target_name)
77
- create_commands(xml, commands, target_name)
150
+ # Get the Telemetry items to avoid clashing parameters
151
+ if telemetry[target_name]
152
+ unique_tlm_params = telemetry[target_name] ? get_unique(telemetry[target_name]) : {}
153
+ else
154
+ unique_tlm_params = {}
155
+ end
156
+ create_commands(xml, commands, target_name, unique_tlm_params)
157
+ create_algorithms(xml, telemetry, target_name)
78
158
  end # SpaceSystem
79
159
  end # builder
80
160
  File.open(filename, 'w') do |file|
@@ -83,8 +163,46 @@ module OpenC3
83
163
  end
84
164
  end
85
165
 
166
+ def create_algorithms(xml, telemetry, target_name)
167
+ return unless telemetry[target_name]
168
+ derived = {}
169
+ telemetry[target_name].each do |packet_name, packet|
170
+ packet.sorted_items.each do |item|
171
+ next if item.data_type != :DERIVED
172
+ next if COSMOS_NATIVE_DERIVED_ITEMS.include?(item.name)
173
+ next if item.name == @packet_time_string
174
+ derived[packet_name] = item
175
+ end
176
+ end
177
+ return unless derived.length > 0
178
+ algorithm_xml = Nokogiri::XML::Builder.new do |alg_xml|
179
+ alg_xml.AlgorithmSet do
180
+ derived.each do |packet_name, item|
181
+ alg_xml.CustomAlgorithm("name" => "#{packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_" \
182
+ "#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_#{item.read_conversion.class.name}") do
183
+ alg_xml.ExternalAlgorithmSet do
184
+ alg_xml.ExternalAlgorithm("implementationName" => "TODO", "algorithmLocation" => "TODO")
185
+ end
186
+ alg_xml.InputSet do
187
+ alg_xml.InputParameterInstanceRef( :parameterRef => "TODO", :instance => "0", :useCalibratedValue => "TODO")
188
+ end
189
+ alg_xml.OutputSet do
190
+ alg_xml.OutputParameterRef( :parameterRef => "#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}")
191
+ end
192
+ alg_xml.TriggerSet( :name => "triggerSet") do
193
+ alg_xml.OnParameterUpdateTrigger( :parameterRef => "TODO")
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ xml['xtce'].comment "TODO \n#{algorithm_xml.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION | Nokogiri::XML::Node::SaveOptions::FORMAT)}\n"
200
+ end
201
+
86
202
  def create_telemetry(xml, telemetry, target_name)
87
203
  # Gather and make unique all the packet items
204
+ return unless telemetry[target_name]
205
+
88
206
  unique_items = telemetry[target_name] ? get_unique(telemetry[target_name]) : {}
89
207
 
90
208
  xml['xtce'].TelemetryMetaData do
@@ -93,31 +211,39 @@ module OpenC3
93
211
  to_xtce_type(item, 'Parameter', xml)
94
212
  end
95
213
  end
214
+ has_packet_time = unique_items.include?(@packet_time_string)
96
215
 
97
216
  xml['xtce'].ParameterSet do
98
217
  unique_items.each do |item_name, item|
99
- to_xtce_item(item, 'Parameter', xml)
218
+ to_xtce_item(item, 'Parameter', xml, has_packet_time: has_packet_time)
100
219
  end
101
220
  end
102
221
 
103
222
  if telemetry[target_name]
104
223
  xml['xtce'].ContainerSet do
105
224
  telemetry[target_name].each do |packet_name, packet|
106
- attrs = { :name => (packet_name + '_Base'), :abstract => "true" }
107
- xml['xtce'].SequenceContainer(attrs) do
108
- process_entry_list(xml, packet, :TELEMETRY)
109
- end
110
-
111
- attrs = { :name => packet_name }
225
+ # Replaces invalid characters if any exist
226
+ attrs = { :name => packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR) }
112
227
  attrs['shortDescription'] = packet.description if packet.description
113
228
  xml['xtce'].SequenceContainer(attrs) do
114
- xml['xtce'].EntryList
115
- xml['xtce'].BaseContainer(:containerRef => (packet_name + '_Base')) do
229
+ # Adds an alias if any invalid characters exist
230
+ if packet_name.count(INVALID_CHARS) > 0
231
+ xml['xtce'].AliasSet do
232
+ xml['xtce'].Alias(:nameSpace => ALIAS_NAMESPACE, :alias => packet_name)
233
+ end
234
+ end
235
+ if packet.short_buffer_allowed
236
+ xml['xtce'].AncillaryDataSet do
237
+ xml['xtce'].AncillaryData("true", :name => "ALLOW_SHORT")
238
+ end
239
+ end
240
+ process_entry_list(xml, packet, :TELEMETRY)
241
+ xml['xtce'].BaseContainer(:containerRef => (packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR))) do
116
242
  if packet.id_items && packet.id_items.length > 0
117
243
  xml['xtce'].RestrictionCriteria do
118
244
  xml['xtce'].ComparisonList do
119
245
  packet.id_items.each do |item|
120
- xml['xtce'].Comparison(:parameterRef => item.name, :value => item.id_value)
246
+ xml['xtce'].Comparison(:parameterRef => item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR), :value => item.id_value)
121
247
  end
122
248
  end
123
249
  end
@@ -130,57 +256,136 @@ module OpenC3
130
256
  end # TelemetryMetaData
131
257
  end
132
258
 
133
- def create_commands(xml, commands, target_name)
259
+ def create_commands(xml, commands, target_name, unique_tlm_params = {})
134
260
  return unless commands[target_name]
135
261
 
136
262
  xml['xtce'].CommandMetaData do
263
+ unique_id_items = get_unique_id_items(commands[target_name])
264
+ # Create Parameters for any ID item so it can be used in a comparison.
265
+ if unique_id_items.size > 0
266
+ xml['xtce'].ParameterTypeSet do
267
+ unique_id_items.each do |item_name, item|
268
+ prefix = unique_tlm_params.include?(item_name) ? "CMD_" : ""
269
+ to_xtce_type(item, 'Parameter', xml, prefix: prefix)
270
+ end
271
+ end
272
+ xml['xtce'].ParameterSet do
273
+ unique_id_items.each do |item_name, item|
274
+ prefix = unique_tlm_params.include?(item_name) ? "CMD_" : ""
275
+ to_xtce_item(item, 'Parameter', xml, prefix: prefix)
276
+ end
277
+ end
278
+ end
279
+ #unique_command_args_without_ids = get_unique_without_ids(commands[target_name])
280
+ #if unique_command_args_without_ids.size > 0
137
281
  xml['xtce'].ArgumentTypeSet do
138
- get_unique(commands[target_name]).each do |arg_name, arg|
139
- to_xtce_type(arg, 'Argument', xml)
282
+ commands[target_name].each do |packet_name, packet|
283
+ packet.items.each do |arg_name, arg|
284
+ next if arg.data_type == :DERIVED
285
+ next if unique_id_items.key?(arg_name.tr(INVALID_CHARS, REPLACEMENT_CHAR))
286
+ to_xtce_type(arg, 'Argument', xml, prefix: packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + "_")
287
+ end
140
288
  end
141
289
  end
142
290
  xml['xtce'].MetaCommandSet do
143
291
  commands[target_name].each do |packet_name, packet|
144
- attrs = { :name => packet_name + "_Base", :abstract => "true" }
145
- xml['xtce'].MetaCommand(attrs) do
146
- xml['xtce'].ArgumentList do
147
- packet.sorted_items.each do |item|
148
- next if item.data_type == :DERIVED
149
-
150
- to_xtce_item(item, 'Argument', xml)
151
- end
152
- end # ArgumentList
153
- xml['xtce'].CommandContainer(:name => "#{target_name}_#{packet_name}_CommandContainer") do
154
- process_entry_list(xml, packet, :COMMAND)
155
- end
156
- end # Abstract MetaCommand
157
-
158
- attrs = { :name => packet_name }
292
+ attrs = { :name => packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR) }
159
293
  attrs['shortDescription'] = packet.description if packet.description
160
294
  xml['xtce'].MetaCommand(attrs) do
161
- xml['xtce'].BaseMetaCommand(:metaCommandRef => packet_name + "_Base") do
295
+ if packet_name.count(INVALID_CHARS) > 0
296
+ xml['xtce'].AliasSet do
297
+ xml['xtce'].Alias(:nameSpace => ALIAS_NAMESPACE, :alias => packet_name)
298
+ end # AliasSet
299
+ end # If packet contains invalid chars
300
+ argument_list_sorted_items = get_sorted_non_id_items(packet.sorted_items)
301
+ if argument_list_sorted_items.size > 0
302
+ xml['xtce'].ArgumentList do
303
+ argument_list_sorted_items.each do |item|
304
+ to_xtce_item(item, 'Argument', xml, prefix: packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + "_")
305
+ end
306
+ end # ArgumentList
307
+ end # If Arguments List is greater than 0
308
+ xml['xtce'].CommandContainer(:name => "#{packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_Commands") do
309
+ process_entry_list(xml, packet, :COMMAND, unique_tlm_params)
310
+ #xml['xtce'].BaseContainer(:containerRef => "#{target_name}_#{packet_name}_CommandContainer")
162
311
  if packet.id_items && packet.id_items.length > 0
163
- xml['xtce'].ArgumentAssignmentList do
164
- packet.id_items.each do |item|
165
- xml['xtce'].ArgumentAssignment(:argumentName => item.name, :argumentValue => item.id_value)
166
- end
167
- end # ArgumentAssignmentList
168
- end
169
- end # BaseMetaCommand
170
- end # Actual MetaCommand
171
- end # commands.each
312
+ packet.id_items.each do |item|
313
+ xml['xtce'].BaseContainer(:containerRef => "#{packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_Commands") do
314
+ xml['xtce'].RestrictionCriteria do
315
+ xml['xtce'].ComparisonList do
316
+ item_prefix = unique_tlm_params.include?(item.name) ? "CMD_" : ""
317
+ xml['xtce'].Comparison(:parameterRef => item_prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR),:value => item.id_value)
318
+ end
319
+ end # Restriction Criteria
320
+ end # Base Container
321
+ end # for each packet ID item
322
+ end # If id items
323
+ end # Command Container
324
+ end # MetaCommand
325
+ end # each command packet
172
326
  end # MetaCommandSet
173
327
  end # CommandMetaData
174
328
  end
175
329
 
330
+ def get_numerical_item_initial_value(item)
331
+ initial_value = nil
332
+ if item.states && item.default && !item.array_size
333
+ # Invert hash so we can get the initial value. If not found remove the initial value.
334
+ inverted_enum_states = item.states.invert
335
+ if inverted_enum_states.include?(item.default)
336
+ initial_value = inverted_enum_states[item.default]
337
+ end
338
+ elsif item.default && !item.array_size
339
+ initial_value = item.default
340
+ end
341
+ if initial_value == "1970-01-01T00:00:00Z"
342
+ initial_value = 0
343
+ end
344
+ initial_value
345
+ end
346
+
347
+ def get_string_or_block_initial_value(item, string_or_binary)
348
+ initial_value = nil
349
+ if item.default && !item.array_size
350
+ unless item.default.is_printable?
351
+ initial_value = '0x' + item.default.simple_formatted
352
+ else
353
+ if string_or_binary == :STRING
354
+ initial_value = item.default.inspect
355
+ else
356
+ initial_value = item.default.inspect.unpack('H*').first
357
+ end
358
+ end
359
+ end
360
+ initial_value
361
+ end
362
+
176
363
  def get_unique(items)
177
364
  unique = {}
178
365
  items.each do |packet_name, packet|
179
366
  packet.sorted_items.each do |item|
180
- next if item.data_type == :DERIVED
367
+ unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] ||= []
368
+ unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] << item
369
+ end
370
+ end
371
+ unique.each do |item_name, unique_items|
372
+ if unique_items.length <= 1
373
+ unique[item_name] = unique_items[0]
374
+ next
375
+ end
376
+ unique[item_name] = unique_items[0]
377
+ end
378
+ unique
379
+ end
181
380
 
182
- unique[item.name] ||= []
183
- unique[item.name] << item
381
+ def get_unique_without_ids(items)
382
+ unique = {}
383
+ items.each do |packet_name, packet|
384
+ packet.sorted_items.each do |item|
385
+ next if item.data_type == :DERIVED
386
+ next if item.id_value
387
+ unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] ||= []
388
+ unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] << item
184
389
  end
185
390
  end
186
391
  unique.each do |item_name, unique_items|
@@ -188,17 +393,45 @@ module OpenC3
188
393
  unique[item_name] = unique_items[0]
189
394
  next
190
395
  end
191
- # TODO: need to make sure all the items in the array are exactly the same
192
396
  unique[item_name] = unique_items[0]
193
397
  end
194
398
  unique
195
399
  end
196
400
 
401
+ def get_sorted_non_id_items(items)
402
+ sorted_items = []
403
+ items.each do |item|
404
+ next if item.data_type == :DERIVED
405
+ next if item.id_value
406
+ sorted_items.push(item)
407
+ end
408
+ sorted_items
409
+ end
410
+
411
+ def get_unique_id_items(items)
412
+ unique = {}
413
+ items.each do |packet_name, packet|
414
+ packet.id_items.each do |item|
415
+ next if item.data_type == :DERIVED
416
+ unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] ||= []
417
+ unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] << item
418
+ end
419
+ end
420
+ unique.each do |item_name, unique_items|
421
+ if unique_items.length <= 1
422
+ unique[item_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] = unique_items[0]
423
+ next
424
+ end
425
+ unique[item_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] = unique_items[0]
426
+ end
427
+ unique
428
+ end
429
+
197
430
  # This method is almost the same for commands and telemetry except for the
198
431
  # XML element name: [Array]ArgumentRefEntry vs [Array]ParameterRefEntry,
199
432
  # and XML reference: argumentRef vs parameterRef.
200
433
  # Thus we build the name and use send to dynamically dispatch.
201
- def process_entry_list(xml, packet, cmd_vs_tlm)
434
+ def process_entry_list(xml, packet, cmd_vs_tlm, unique_tlm_params = {})
202
435
  if cmd_vs_tlm == :COMMAND
203
436
  type = "Argument"
204
437
  else # :TELEMETRY
@@ -208,11 +441,12 @@ module OpenC3
208
441
  packed = packet.packed?
209
442
  packet.sorted_items.each do |item|
210
443
  next if item.data_type == :DERIVED
211
-
212
- # TODO: Handle nonunique item names
444
+ temp_type = item.id_value ? "Parameter" : type
445
+ prefix = (cmd_vs_tlm == :COMMAND && unique_tlm_params.include?(item.name)) ? "CMD_" : ""
213
446
  if item.array_size
214
447
  # Requiring parameterRef for argument arrays appears to be a defect in the schema
215
- xml['xtce'].public_send("Array#{type}RefEntry".intern, :parameterRef => item.name) do
448
+ reference_symbol = temp_type == "Argument" ? :parameterRef : "#{temp_type.downcase}Ref".to_sym
449
+ xml['xtce'].public_send("Array#{temp_type}RefEntry".intern, reference_symbol => prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)) do
216
450
  set_fixed_value(xml, item) if !packed
217
451
  xml['xtce'].DimensionList do
218
452
  xml['xtce'].Dimension do
@@ -227,9 +461,9 @@ module OpenC3
227
461
  end
228
462
  else
229
463
  if packed
230
- xml['xtce'].public_send("#{type}RefEntry".intern, "#{type.downcase}Ref".intern => item.name)
464
+ xml['xtce'].public_send("#{temp_type}RefEntry".intern, "#{temp_type.downcase}Ref".intern => prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR))
231
465
  else
232
- xml['xtce'].public_send("#{type}RefEntry".intern, "#{type.downcase}Ref".intern => item.name) do
466
+ xml['xtce'].public_send("#{temp_type}RefEntry".intern, "#{temp_type.downcase}Ref".intern => prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)) do
233
467
  set_fixed_value(xml, item)
234
468
  end
235
469
  end
@@ -250,29 +484,45 @@ module OpenC3
250
484
  end
251
485
  end
252
486
 
253
- def to_xtce_type(item, param_or_arg, xml)
254
- # TODO: Spline Conversions
487
+ def to_xtce_type(item, param_or_arg, xml, prefix: "")
255
488
  case item.data_type
256
489
  when :INT, :UINT
257
- to_xtce_int(item, param_or_arg, xml)
490
+ to_xtce_int(item, param_or_arg, xml, prefix:prefix)
258
491
  when :FLOAT
259
- to_xtce_float(item, param_or_arg, xml)
492
+ to_xtce_float(item, param_or_arg, xml, prefix: prefix)
260
493
  when :STRING
261
- to_xtce_string(item, param_or_arg, xml, 'String')
494
+ to_xtce_string(item, param_or_arg, xml, 'String', prefix: prefix)
262
495
  when :BLOCK
263
- to_xtce_string(item, param_or_arg, xml, 'Binary')
264
- else
265
- raise "#{item.data_type} data type not supported in XTCE"
496
+ to_xtce_string(item, param_or_arg, xml, 'Binary', prefix: prefix)
497
+ when :DERIVED
498
+ if !COSMOS_NATIVE_DERIVED_ITEMS.include?(item.name)
499
+ to_xtce_derived(item, param_or_arg, xml, prefix: prefix)
500
+ end
266
501
  end
267
502
 
268
503
  # Handle arrays
269
504
  if item.array_size
270
505
  # The above will have created the type for the array entries. Now we create the type for the actual array.
271
- attrs = { :name => (item.name + '_ArrayType') }
506
+
507
+ attrs = { :name => (prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + '_ArrayType') }
272
508
  attrs[:shortDescription] = item.description if item.description
273
- attrs[:arrayTypeRef] = (item.name + '_Type')
274
- attrs[:numberOfDimensions] = '1' # OpenC3 Only supports one-dimensional arrays
275
- xml['xtce'].public_send('Array' + param_or_arg + 'Type', attrs)
509
+ attrs[:arrayTypeRef] = (prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + '_Type')
510
+ xml['xtce'].public_send('Array' + param_or_arg + 'Type', attrs) do
511
+ xml['xtce'].DimensionList do
512
+ xml['xtce'].Dimension do
513
+ xml['xtce'].StartingIndex do
514
+ xml['xtce'].FixedValue do
515
+ xml['xtce'].text 0
516
+ end # FixedValue
517
+ end # StartingIndex
518
+ xml['xtce'].EndingIndex do
519
+ xml['xtce'].FixedValue do
520
+ xml['xtce'].text 0 # OpenC3 Only supports one-dimensional arrays
521
+ end # FixedValue
522
+ end # EndingIndex
523
+ end # Dimension
524
+ end # DimensionList
525
+ end # Array<param_or_arg>Type
276
526
  end
277
527
  end
278
528
 
@@ -291,25 +541,38 @@ module OpenC3
291
541
  end
292
542
  end
293
543
 
294
- def to_xtce_int(item, param_or_arg, xml)
295
- attrs = { :name => (item.name + '_Type') }
544
+ def to_xtce_int(item, param_or_arg, xml, prefix: "")
545
+ attrs = { :name => (prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + '_Type') }
296
546
  attrs[:initialValue] = item.default if item.default and !item.array_size
297
547
  attrs[:shortDescription] = item.description if item.description
548
+ if attrs[:initialValue] == "1970-01-01T00:00:00Z"
549
+ attrs[:initialValue] = "0"
550
+ end
298
551
  if item.states and item.default and item.states.key(item.default)
299
552
  attrs[:initialValue] = item.states.key(item.default) and !item.array_size
300
553
  end
301
554
  if item.data_type == :INT
302
555
  signed = 'true'
303
- encoding = 'twosCompliment'
556
+ encoding = 'twosComplement'
304
557
  else
305
558
  signed = 'false'
306
559
  encoding = 'unsigned'
307
560
  end
308
561
  if item.states
562
+ # Invert hash so we can get the initial value. If not found remove the initial value.
563
+ inverted_enum_states = item.states.invert
564
+ if inverted_enum_states.include?(item.default)
565
+ attrs[:initialValue] = inverted_enum_states[item.default]
566
+ else
567
+ attrs.delete(:initialValue)
568
+ end
309
569
  xml['xtce'].public_send('Enumerated' + param_or_arg + 'Type', attrs) do
310
- to_xtce_endianness(item, xml)
311
570
  to_xtce_units(item, xml)
312
- xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
571
+ if item.endianness == :LITTLE_ENDIAN and item.bit_size > 8
572
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding, :byteOrder => "leastSignificantByteFirst")
573
+ else
574
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
575
+ end
313
576
  xml['xtce'].EnumerationList do
314
577
  item.states.each do |state_name, state_value|
315
578
  # Skip the special OpenC3 'ANY' enumerated state
@@ -327,53 +590,86 @@ module OpenC3
327
590
  attrs[:signed] = signed
328
591
  end
329
592
  xml['xtce'].public_send(type_string, attrs) do
330
- to_xtce_endianness(item, xml)
331
593
  to_xtce_units(item, xml)
332
594
  if (item.read_conversion and item.read_conversion.class == PolynomialConversion) or (item.write_conversion and item.write_conversion.class == PolynomialConversion)
333
- xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding) do
334
- to_xtce_conversion(item, xml)
595
+ if item.endianness == :LITTLE_ENDIAN and item.bit_size >= 8
596
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding, :byteOrder => "leastSignificantByteFirst") do
597
+ to_xtce_conversion(item, xml)
598
+ end
599
+ else
600
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding) do
601
+ to_xtce_conversion(item, xml)
602
+ end
335
603
  end
336
604
  else
337
- xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
605
+ if item.endianness == :LITTLE_ENDIAN and item.bit_size >= 8
606
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding, :byteOrder => "leastSignificantByteFirst")
607
+ else
608
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
609
+ end
338
610
  end
339
611
  to_xtce_limits(item, xml)
340
- if item.range
341
- xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
612
+ if item.range and item.range.last <= MAX_64_BIT_INT
613
+ if param_or_arg == "Parameter"
614
+ xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
615
+ else
616
+ xml['xtce'].ValidRangeSet do
617
+ xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
618
+ end
619
+ end
342
620
  end
343
621
  end # Type
344
622
  end # if item.states
345
623
  end
346
624
 
347
- def to_xtce_float(item, param_or_arg, xml)
348
- attrs = { :name => (item.name + '_Type'), :sizeInBits => item.bit_size }
625
+ def to_xtce_float(item, param_or_arg, xml, prefix: "")
626
+ attrs = { :name => (prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + '_Type'), :sizeInBits => item.bit_size }
349
627
  attrs[:initialValue] = item.default if item.default and !item.array_size
350
628
  attrs[:shortDescription] = item.description if item.description
351
629
  xml['xtce'].public_send('Float' + param_or_arg + 'Type', attrs) do
352
- to_xtce_endianness(item, xml)
353
630
  to_xtce_units(item, xml)
354
631
  if (item.read_conversion and item.read_conversion.class == PolynomialConversion) or (item.write_conversion and item.write_conversion.class == PolynomialConversion)
355
- xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985') do
632
+ if item.endianness == :LITTLE_ENDIAN and item.bit_size >= 8
633
+ xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985', :byteOrder => "leastSignificantByteFirst") do
356
634
  to_xtce_conversion(item, xml)
357
635
  end
636
+ else
637
+ xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985') do
638
+ to_xtce_conversion(item, xml)
639
+ end
640
+ end
358
641
  else
359
- xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985')
642
+ if item.endianness == :LITTLE_ENDIAN and item.bit_size >= 8
643
+ xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985', :byteOrder => "leastSignificantByteFirst")
644
+ else
645
+ xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985')
646
+ end
360
647
  end
361
648
  to_xtce_limits(item, xml)
362
- if item.range
363
- xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
649
+ if item.range and item.range.last < MAX_64_BIT_INT
650
+ if param_or_arg == "Parameter"
651
+ xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
652
+ else
653
+ xml['xtce'].ValidRangeSet do
654
+ xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
655
+ end
656
+ end
364
657
  end
365
658
  end
366
659
  end
367
660
 
368
- def to_xtce_string(item, param_or_arg, xml, string_or_binary)
369
- # TODO: OpenC3 Variably sized strings are not supported in XTCE
370
- attrs = { :name => (item.name + '_Type') }
661
+ def to_xtce_string(item, param_or_arg, xml, string_or_binary, prefix: "")
662
+ attrs = { :name => (prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + '_Type') }
371
663
  attrs[:characterWidth] = 8 if string_or_binary == 'String'
372
664
  if item.default && !item.array_size
373
665
  unless item.default.is_printable?
374
666
  attrs[:initialValue] = '0x' + item.default.simple_formatted
375
667
  else
376
- attrs[:initialValue] = item.default.inspect
668
+ if string_or_binary == 'String'
669
+ attrs[:initialValue] = item.default.inspect
670
+ else
671
+ attrs[:initialValue] = item.default.inspect.unpack('H*').first
672
+ end
377
673
  end
378
674
  end
379
675
  attrs[:shortDescription] = item.description if item.description
@@ -384,10 +680,15 @@ module OpenC3
384
680
  xml['xtce'].StringDataEncoding(:encoding => 'UTF-8') do
385
681
  xml['xtce'].SizeInBits do
386
682
  xml['xtce'].Fixed do
387
- xml['xtce'].FixedValue(item.bit_size.to_s)
388
- end
389
- end
390
- end
683
+ if item.bit_size != 0
684
+ xml['xtce'].FixedValue(item.bit_size.to_s)
685
+ else
686
+ xml['xtce'].FixedValue(DYNAMIC_STRING_LEN)
687
+ end # if statement
688
+ end # </Fixed>
689
+ xml['xtce'].TerminationChar("00")
690
+ end # </SizeInBits>
691
+ end # </StringDataEncoding>
391
692
  else
392
693
  xml['xtce'].BinaryDataEncoding do
393
694
  xml['xtce'].SizeInBits do
@@ -398,11 +699,74 @@ module OpenC3
398
699
  end
399
700
  end
400
701
 
401
- def to_xtce_item(item, param_or_arg, xml)
702
+ def to_xtce_derived(item, param_or_arg, xml, prefix: "")
703
+ if item.name == @packet_time_string
704
+ xml << "\n<!--TODO: \n" \
705
+ "\t<xtce:AbsoluteTime#{param_or_arg}Type name=\"#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_Type\">\n" \
706
+ "\t\t<TODO/>\n" \
707
+ "\t</xtce:AbsoluteTime#{param_or_arg}Type>"
708
+ "-->\n"
709
+ else
710
+ description_string = item.description ? "shortDescription=\"#{item.description}\"" : ""
711
+ xml << "\n<!--TODO: \n" \
712
+ "\t<xtce:TODO#{param_or_arg}Type name=\"#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_Type\" #{description_string} />\n" \
713
+ "-->\n"
714
+ end
715
+ end
716
+
717
+ def to_xtce_item(item, param_or_arg, xml, prefix: "", has_packet_time: false)
718
+ replaced_item_name = prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)
402
719
  if item.array_size
403
- xml['xtce'].public_send(param_or_arg, :name => item.name, "#{param_or_arg.downcase}TypeRef" => item.name + '_ArrayType')
720
+ type_suffix = "_ArrayType"
721
+ else
722
+ type_suffix = "_Type"
723
+ end
724
+ attrs = {:name => replaced_item_name, "#{param_or_arg.downcase}TypeRef" => replaced_item_name + type_suffix}
725
+ needs_alias = item.name.count(INVALID_CHARS) > 0 || !prefix.empty?
726
+ if param_or_arg.downcase == "argument"
727
+ # Set the name to just be the item name since ArgumentsTypes
728
+ # will use the packet name as a prefix but not in the actual argument name.
729
+ # Maintains the individual type between arguments with a shared name.
730
+ needs_alias = item.name.count(INVALID_CHARS) > 0
731
+ attrs[:name] = item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)
732
+ initial_value = case item.data_type
733
+ when :INT, :UINT, :FLOAT
734
+ get_numerical_item_initial_value(item)
735
+ when :STRING, :BLOCK
736
+ get_string_or_block_initial_value(item, item.data_type)
737
+ when :DERIVED
738
+ nil
739
+ end
740
+ attrs[:initialValue] = initial_value
741
+ end
742
+ if item.data_type == :DERIVED
743
+ if COSMOS_NATIVE_DERIVED_ITEMS.include?(item.name)
744
+ return
745
+ end
746
+ parameter_comment = "\n<!-- TODO: \n" \
747
+ "\t<xtce:#{param_or_arg} name=\"#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}\" #{param_or_arg.downcase}TypeRef=\"#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}_Type\">\n" \
748
+ "\t\t<xtce:ParameterProperties dataSource=\"derived\"/>\n"
749
+
750
+ if needs_alias
751
+ parameter_comment += "\t\t<xtce:AliasSet>\n" \
752
+ "\t\t\t<xtce:Alias nameSpace=\"COSMOS\" alias=\"#{item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)}\"/>\n" \
753
+ "\t\t</xtce:AliasSet>\n"
754
+ end
755
+ parameter_comment += "\t</xtce:#{param_or_arg}>\n-->\n"
756
+ xml << parameter_comment
404
757
  else
405
- xml['xtce'].public_send(param_or_arg, :name => item.name, "#{param_or_arg.downcase}TypeRef" => item.name + '_Type')
758
+ xml['xtce'].public_send(param_or_arg, attrs) do
759
+ if needs_alias
760
+ xml['xtce'].AliasSet do
761
+ xml['xtce'].Alias(:nameSpace => ALIAS_NAMESPACE, :alias => item.name)
762
+ end
763
+ end
764
+ if has_packet_time
765
+ xml['xtce'].ParameterProperties do
766
+ xml['xtce'].TimeAssociation(:parameterRef => @packet_time_string)
767
+ end
768
+ end
769
+ end
406
770
  end
407
771
  end
408
772
 
@@ -437,10 +801,10 @@ module OpenC3
437
801
  xml['xtce'].PolynomialCalibrator do
438
802
  conversion.coeffs.each_with_index do |coeff, index|
439
803
  xml['xtce'].Term(:coefficient => coeff, :exponent => index)
440
- end
441
- end
442
- end
443
- end
804
+ end # for each loop
805
+ end # </PolynomialCalibrator>
806
+ end # </DefaultCalibrator>
807
+ end # if PolynomialConversion
444
808
  end
445
809
  end
446
810
  end