openc3 7.2.0 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/openc3cli +108 -29
- data/bin/pipinstall +5 -4
- data/bin/pipuninstall +5 -1
- data/bin/uvinstall +149 -0
- data/data/config/interface_modifiers.yaml +15 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +5 -1
- data/data/config/screen.yaml +2 -1
- data/data/config/target.yaml +38 -0
- data/data/config/tool.yaml +3 -1
- data/data/config/widgets.yaml +43 -3
- data/data/xtce_schemas/SpaceSystem_20180204.xsd +5918 -0
- data/data/xtce_schemas/xml.xsd +116 -0
- data/ext/openc3/ext/telemetry/telemetry.c +1 -1
- data/lib/openc3/accessors/binary_accessor.rb +4 -0
- data/lib/openc3/accessors/template_accessor.rb +2 -2
- data/lib/openc3/api/cmd_api.rb +5 -1
- data/lib/openc3/api/interface_api.rb +2 -2
- data/lib/openc3/api/limits_api.rb +89 -3
- data/lib/openc3/api/router_api.rb +3 -3
- data/lib/openc3/api/tlm_api.rb +1 -1
- data/lib/openc3/interfaces/http_client_interface.rb +1 -1
- data/lib/openc3/io/json_drb.rb +24 -8
- data/lib/openc3/io/json_rpc.rb +0 -2
- data/lib/openc3/logs/log_writer.rb +43 -7
- data/lib/openc3/logs/packet_log_writer.rb +1 -2
- data/lib/openc3/microservices/decom_common.rb +15 -4
- data/lib/openc3/microservices/decom_microservice.rb +6 -1
- data/lib/openc3/microservices/interface_microservice.rb +26 -12
- data/lib/openc3/microservices/queue_microservice.rb +4 -2
- data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +55 -0
- data/lib/openc3/migrations/20260731000000_system_health_json.rb +41 -0
- data/lib/openc3/models/cvt_model.rb +7 -7
- data/lib/openc3/models/db_sharded_model.rb +2 -2
- data/lib/openc3/models/gem_model.rb +1 -1
- data/lib/openc3/models/interface_model.rb +17 -11
- data/lib/openc3/models/interface_status_model.rb +2 -2
- data/lib/openc3/models/metric_model.rb +2 -2
- data/lib/openc3/models/microservice_model.rb +21 -0
- data/lib/openc3/models/microservice_status_model.rb +2 -2
- data/lib/openc3/models/model.rb +2 -6
- data/lib/openc3/models/plugin_model.rb +204 -49
- data/lib/openc3/models/python_package_model.rb +210 -12
- data/lib/openc3/models/reingest_job_model.rb +1 -1
- data/lib/openc3/models/scope_model.rb +34 -30
- data/lib/openc3/models/script_status_model.rb +73 -38
- data/lib/openc3/models/target_model.rb +193 -6
- data/lib/openc3/models/tool_model.rb +33 -20
- data/lib/openc3/operators/microservice_operator.rb +33 -4
- data/lib/openc3/operators/operator.rb +15 -5
- data/lib/openc3/packets/commands.rb +7 -7
- data/lib/openc3/packets/limits.rb +33 -2
- data/lib/openc3/packets/packet.rb +6 -4
- data/lib/openc3/packets/packet_config.rb +5 -1
- data/lib/openc3/packets/parsers/state_parser.rb +1 -1
- data/lib/openc3/packets/parsers/xtce_converter.rb +183 -103
- data/lib/openc3/packets/parsers/xtce_parser.rb +71 -23
- data/lib/openc3/packets/structure.rb +16 -12
- data/lib/openc3/packets/telemetry.rb +3 -3
- data/lib/openc3/script/commands.rb +1 -1
- data/lib/openc3/script/extract.rb +20 -25
- data/lib/openc3/script/limits.rb +1 -1
- data/lib/openc3/script/suite_runner.rb +43 -9
- data/lib/openc3/script/web_socket_api.rb +45 -5
- data/lib/openc3/system/target.rb +4 -1
- data/lib/openc3/tools/table_manager/table_config.rb +4 -1
- data/lib/openc3/top_level.rb +1 -1
- data/lib/openc3/topics/command_decom_topic.rb +16 -1
- data/lib/openc3/topics/limits_event_topic.rb +63 -4
- data/lib/openc3/topics/telemetry_decom_topic.rb +2 -2
- data/lib/openc3/utilities/aws_bucket.rb +10 -2
- data/lib/openc3/utilities/cli_generator.rb +5 -1
- data/lib/openc3/utilities/csv.rb +5 -5
- data/lib/openc3/utilities/env_helper.rb +13 -1
- data/lib/openc3/utilities/local_mode.rb +4 -1
- data/lib/openc3/utilities/pypi_url.rb +39 -0
- data/lib/openc3/utilities/reingest_job.rb +1 -1
- data/lib/openc3/utilities/ruby_lex_utils.rb +5 -1
- data/lib/openc3/utilities/running_script.rb +160 -100
- data/lib/openc3/utilities/script.rb +60 -13
- data/lib/openc3/utilities/store_autoload.rb +39 -1
- data/lib/openc3/utilities/store_queued.rb +5 -1
- data/lib/openc3/version.rb +5 -5
- data/lib/openc3.rb +6 -1
- data/templates/plugin/plugin.gemspec +1 -1
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +7 -3
- data/templates/tool_vue/.nycrc +0 -3
- data/templates/widget/.nycrc +0 -3
|
@@ -24,15 +24,46 @@ INVALID_CHARS = '[]./'
|
|
|
24
24
|
REPLACEMENT_CHAR = '_'
|
|
25
25
|
ALIAS_NAMESPACE = 'COSMOS'
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
# IntegerRangeType declares minInclusive / maxInclusive as xs:long, so an integer range
|
|
28
|
+
# outside these bounds (a full 64 bit UINT, for example) cannot be expressed as a
|
|
29
|
+
# ValidRange. FloatRangeType uses xs:double and needs no such clamp.
|
|
30
|
+
XS_LONG_MIN = -9223372036854775808
|
|
31
|
+
XS_LONG_MAX = 9223372036854775807
|
|
29
32
|
COSMOS_NATIVE_DERIVED_ITEMS = ['PACKET_TIMESECONDS', 'PACKET_TIMEFORMATTED', 'RECEIVED_TIMESECONDS', 'RECEIVED_TIMEFORMATTED', 'RECEIVED_COUNT']
|
|
30
|
-
PACKET_TIME_STRING = "PACKET_TIME"
|
|
31
33
|
|
|
32
34
|
module OpenC3
|
|
33
35
|
class XtceConverter
|
|
34
36
|
attr_accessor :current_target_name
|
|
35
37
|
|
|
38
|
+
# The namespace COSMOS exports. Files declaring an older namespace still import.
|
|
39
|
+
XTCE_1_2_NAMESPACE = 'http://www.omg.org/spec/XTCE/20180204'
|
|
40
|
+
# The OMG schemas are vendored here and validation reads them from disk, so it works
|
|
41
|
+
# air gapped. The omg.org URL the files declare in schemaLocation is never fetched.
|
|
42
|
+
SCHEMA_DIR = File.join(PATH, 'data', 'xtce_schemas')
|
|
43
|
+
SCHEMA_FILE = File.join(SCHEMA_DIR, 'SpaceSystem_20180204.xsd')
|
|
44
|
+
|
|
45
|
+
# The XTCE namespace an existing .xtce file declares on its root element
|
|
46
|
+
#
|
|
47
|
+
# @param filename [String] Path to a .xtce file
|
|
48
|
+
# @return [String, nil] The declared namespace, or nil if the file has none
|
|
49
|
+
def self.xtce_namespace(filename)
|
|
50
|
+
Nokogiri::XML(File.read(filename)).root&.namespace&.href
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Validate an .xtce file against the vendored OMG XTCE 1.2 schema
|
|
54
|
+
#
|
|
55
|
+
# Only meaningful for files declaring XTCE_1_2_NAMESPACE. XTCE 1.0 / 1.1 files
|
|
56
|
+
# import fine but report errors against this schema, so check xtce_namespace first.
|
|
57
|
+
#
|
|
58
|
+
# @param filename [String] Path to a .xtce file
|
|
59
|
+
# @return [Array<String>] Validation error messages, empty when the file is valid
|
|
60
|
+
def self.schema_errors(filename)
|
|
61
|
+
# File.open, not File.read: the schema imports xml.xsd relative to itself, which
|
|
62
|
+
# Nokogiri can only resolve from the IO's path
|
|
63
|
+
@schema ||= File.open(SCHEMA_FILE) { |f| Nokogiri::XML::Schema(f) }
|
|
64
|
+
@schema.validate(Nokogiri::XML(File.read(filename))).map(&:message)
|
|
65
|
+
end
|
|
66
|
+
|
|
36
67
|
# Output a previously parsed definition file into the XTCE format
|
|
37
68
|
#
|
|
38
69
|
# @param commands [Hash<String=>Packet>] Hash of all the command packets
|
|
@@ -206,19 +237,24 @@ module OpenC3
|
|
|
206
237
|
# Gather and make unique all the packet items
|
|
207
238
|
return unless telemetry[target_name]
|
|
208
239
|
|
|
209
|
-
unique_items =
|
|
240
|
+
unique_items = get_unique(telemetry[target_name])
|
|
241
|
+
has_packet_time = unique_items.include?(@packet_time_string)
|
|
210
242
|
|
|
211
243
|
xml['xtce'].TelemetryMetaData do
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
244
|
+
# The schema requires at least one child in each set, so emit neither when the
|
|
245
|
+
# target's packets hold nothing but DERIVED items. DERIVED items produce a TODO
|
|
246
|
+
# comment at most, never a ParameterType, so they don't count here.
|
|
247
|
+
if unique_items.any? { |_item_name, item| item.data_type != :DERIVED }
|
|
248
|
+
xml['xtce'].ParameterTypeSet do
|
|
249
|
+
unique_items.each do |item_name, item|
|
|
250
|
+
to_xtce_type(item, 'Parameter', xml)
|
|
251
|
+
end
|
|
215
252
|
end
|
|
216
|
-
end
|
|
217
|
-
has_packet_time = unique_items.include?(@packet_time_string)
|
|
218
253
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
254
|
+
xml['xtce'].ParameterSet do
|
|
255
|
+
unique_items.each do |item_name, item|
|
|
256
|
+
to_xtce_item(item, 'Parameter', xml, has_packet_time: has_packet_time)
|
|
257
|
+
end
|
|
222
258
|
end
|
|
223
259
|
end
|
|
224
260
|
|
|
@@ -228,6 +264,20 @@ module OpenC3
|
|
|
228
264
|
# Replaces invalid characters if any exist
|
|
229
265
|
attrs = { :name => packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR) }
|
|
230
266
|
attrs['shortDescription'] = packet.description if packet.description
|
|
267
|
+
container_name = packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)
|
|
268
|
+
has_id_items = (packet.id_items && packet.id_items.length > 0)
|
|
269
|
+
|
|
270
|
+
# RestrictionCriteria only exists on a BaseContainer, so a packet with ID
|
|
271
|
+
# items needs something to inherit from: an abstract container holding the
|
|
272
|
+
# entries, which the concrete container then restricts. Pointing the
|
|
273
|
+
# BaseContainer at its own container instead would make it inherit from
|
|
274
|
+
# itself, which is a cycle for any consumer that resolves inheritance.
|
|
275
|
+
if has_id_items
|
|
276
|
+
xml['xtce'].SequenceContainer(:name => "#{container_name}_Base", :abstract => "true") do
|
|
277
|
+
process_entry_list(xml, packet, :TELEMETRY)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
231
281
|
xml['xtce'].SequenceContainer(attrs) do
|
|
232
282
|
# Adds an alias if any invalid characters exist
|
|
233
283
|
if packet_name.count(INVALID_CHARS) > 0
|
|
@@ -240,9 +290,10 @@ module OpenC3
|
|
|
240
290
|
xml['xtce'].AncillaryData("true", :name => "ALLOW_SHORT")
|
|
241
291
|
end
|
|
242
292
|
end
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
293
|
+
if has_id_items
|
|
294
|
+
# The entries come from the base container. EntryList is required.
|
|
295
|
+
xml['xtce'].EntryList
|
|
296
|
+
xml['xtce'].BaseContainer(:containerRef => "#{container_name}_Base") do
|
|
246
297
|
xml['xtce'].RestrictionCriteria do
|
|
247
298
|
xml['xtce'].ComparisonList do
|
|
248
299
|
packet.id_items.each do |item|
|
|
@@ -251,6 +302,8 @@ module OpenC3
|
|
|
251
302
|
end
|
|
252
303
|
end
|
|
253
304
|
end
|
|
305
|
+
else
|
|
306
|
+
process_entry_list(xml, packet, :TELEMETRY)
|
|
254
307
|
end
|
|
255
308
|
end # SequenceContainer
|
|
256
309
|
end # telemetry.each
|
|
@@ -279,8 +332,6 @@ module OpenC3
|
|
|
279
332
|
end
|
|
280
333
|
end
|
|
281
334
|
end
|
|
282
|
-
#unique_command_args_without_ids = get_unique_without_ids(commands[target_name])
|
|
283
|
-
#if unique_command_args_without_ids.size > 0
|
|
284
335
|
xml['xtce'].ArgumentTypeSet do
|
|
285
336
|
commands[target_name].each do |packet_name, packet|
|
|
286
337
|
packet.items.each do |arg_name, arg|
|
|
@@ -292,36 +343,64 @@ module OpenC3
|
|
|
292
343
|
end
|
|
293
344
|
xml['xtce'].MetaCommandSet do
|
|
294
345
|
commands[target_name].each do |packet_name, packet|
|
|
295
|
-
|
|
346
|
+
command_name = packet_name.tr(INVALID_CHARS, REPLACEMENT_CHAR)
|
|
347
|
+
attrs = { :name => command_name }
|
|
296
348
|
attrs['shortDescription'] = packet.description if packet.description
|
|
349
|
+
argument_list_sorted_items = get_sorted_non_id_items(packet.sorted_items)
|
|
350
|
+
has_id_items = (packet.id_items && packet.id_items.length > 0)
|
|
351
|
+
|
|
352
|
+
# Like telemetry above, the ID comparisons live in a RestrictionCriteria,
|
|
353
|
+
# which only exists on a BaseContainer, so emit an abstract MetaCommand
|
|
354
|
+
# holding the arguments and entries for the concrete one to inherit and
|
|
355
|
+
# restrict. Referencing its own CommandContainer would be a cycle.
|
|
356
|
+
if has_id_items
|
|
357
|
+
xml['xtce'].MetaCommand(:name => "#{command_name}_Base", :abstract => "true") do
|
|
358
|
+
if argument_list_sorted_items.size > 0
|
|
359
|
+
xml['xtce'].ArgumentList do
|
|
360
|
+
argument_list_sorted_items.each do |item|
|
|
361
|
+
to_xtce_item(item, 'Argument', xml, prefix: command_name + "_")
|
|
362
|
+
end
|
|
363
|
+
end # ArgumentList
|
|
364
|
+
end
|
|
365
|
+
xml['xtce'].CommandContainer(:name => "#{command_name}_CommandsBase") do
|
|
366
|
+
process_entry_list(xml, packet, :COMMAND, unique_tlm_params)
|
|
367
|
+
end # Command Container
|
|
368
|
+
end # MetaCommand
|
|
369
|
+
end
|
|
370
|
+
|
|
297
371
|
xml['xtce'].MetaCommand(attrs) do
|
|
298
372
|
if packet_name.count(INVALID_CHARS) > 0
|
|
299
373
|
xml['xtce'].AliasSet do
|
|
300
374
|
xml['xtce'].Alias(:nameSpace => ALIAS_NAMESPACE, :alias => packet_name)
|
|
301
375
|
end # AliasSet
|
|
302
376
|
end # If packet contains invalid chars
|
|
303
|
-
|
|
304
|
-
|
|
377
|
+
if has_id_items
|
|
378
|
+
xml['xtce'].BaseMetaCommand(:metaCommandRef => "#{command_name}_Base")
|
|
379
|
+
elsif argument_list_sorted_items.size > 0
|
|
305
380
|
xml['xtce'].ArgumentList do
|
|
306
381
|
argument_list_sorted_items.each do |item|
|
|
307
|
-
to_xtce_item(item, 'Argument', xml, prefix:
|
|
382
|
+
to_xtce_item(item, 'Argument', xml, prefix: command_name + "_")
|
|
308
383
|
end
|
|
309
384
|
end # ArgumentList
|
|
310
385
|
end # If Arguments List is greater than 0
|
|
311
|
-
xml['xtce'].CommandContainer(:name => "#{
|
|
312
|
-
|
|
313
|
-
#
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
386
|
+
xml['xtce'].CommandContainer(:name => "#{command_name}_Commands") do
|
|
387
|
+
if has_id_items
|
|
388
|
+
# The entries come from the base container. EntryList is required.
|
|
389
|
+
xml['xtce'].EntryList
|
|
390
|
+
# A CommandContainer allows only one BaseContainer, so emit all ID
|
|
391
|
+
# comparisons in a single RestrictionCriteria/ComparisonList.
|
|
392
|
+
xml['xtce'].BaseContainer(:containerRef => "#{command_name}_CommandsBase") do
|
|
393
|
+
xml['xtce'].RestrictionCriteria do
|
|
394
|
+
xml['xtce'].ComparisonList do
|
|
395
|
+
packet.id_items.each do |item|
|
|
319
396
|
item_prefix = unique_tlm_params.include?(item.name) ? "CMD_" : ""
|
|
320
397
|
xml['xtce'].Comparison(:parameterRef => item_prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR),:value => item.id_value)
|
|
321
398
|
end
|
|
322
|
-
end
|
|
323
|
-
end #
|
|
324
|
-
end #
|
|
399
|
+
end
|
|
400
|
+
end # Restriction Criteria
|
|
401
|
+
end # Base Container
|
|
402
|
+
else
|
|
403
|
+
process_entry_list(xml, packet, :COMMAND, unique_tlm_params)
|
|
325
404
|
end # If id items
|
|
326
405
|
end # Command Container
|
|
327
406
|
end # MetaCommand
|
|
@@ -347,17 +426,34 @@ module OpenC3
|
|
|
347
426
|
initial_value
|
|
348
427
|
end
|
|
349
428
|
|
|
429
|
+
# Ending index of an array item's single dimension. XTCE indices are inclusive, so
|
|
430
|
+
# an array of N elements ends at N - 1. A non-positive bit size means a variable
|
|
431
|
+
# length array whose element count isn't known at export time, reported as a single
|
|
432
|
+
# element.
|
|
433
|
+
def array_ending_index(item)
|
|
434
|
+
return 0 if item.bit_size <= 0 || item.array_size <= 0
|
|
435
|
+
|
|
436
|
+
(item.array_size / item.bit_size) - 1
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Initial value for a String or Binary type. Pass :BLOCK / 'Binary' for binary
|
|
440
|
+
# items and :STRING / 'String' for string items.
|
|
350
441
|
def get_string_or_block_initial_value(item, string_or_binary)
|
|
351
442
|
initial_value = nil
|
|
352
443
|
if item.default && !item.array_size
|
|
353
|
-
|
|
444
|
+
if string_or_binary == :BLOCK || string_or_binary == 'Binary'
|
|
445
|
+
# Binary initialValue is xs:hexBinary: raw hex digits, no 0x prefix
|
|
446
|
+
initial_value = item.default.simple_formatted
|
|
447
|
+
elsif !item.default.is_printable? || item.default.upcase.start_with?('0X')
|
|
448
|
+
# XML can't carry the non printable bytes of a string default, so COSMOS
|
|
449
|
+
# writes them as a 0x prefixed hex escape. A printable default that starts
|
|
450
|
+
# with 0x has to be escaped the same way, otherwise the importer reads it
|
|
451
|
+
# back as binary and "0xABCD" round trips as the bytes \xAB\xCD.
|
|
354
452
|
initial_value = '0x' + item.default.simple_formatted
|
|
355
453
|
else
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
initial_value = item.default.inspect.unpack('H*').first
|
|
360
|
-
end
|
|
454
|
+
# String initialValue is the value itself. Quoting it (inspect) would make
|
|
455
|
+
# the quotes part of the default for anything but our own importer.
|
|
456
|
+
initial_value = item.default
|
|
361
457
|
end
|
|
362
458
|
end
|
|
363
459
|
initial_value
|
|
@@ -381,26 +477,6 @@ module OpenC3
|
|
|
381
477
|
unique
|
|
382
478
|
end
|
|
383
479
|
|
|
384
|
-
def get_unique_without_ids(items)
|
|
385
|
-
unique = {}
|
|
386
|
-
items.each do |packet_name, packet|
|
|
387
|
-
packet.sorted_items.each do |item|
|
|
388
|
-
next if item.data_type == :DERIVED
|
|
389
|
-
next if item.id_value
|
|
390
|
-
unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] ||= []
|
|
391
|
-
unique[item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)] << item
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
unique.each do |item_name, unique_items|
|
|
395
|
-
if unique_items.length <= 1
|
|
396
|
-
unique[item_name] = unique_items[0]
|
|
397
|
-
next
|
|
398
|
-
end
|
|
399
|
-
unique[item_name] = unique_items[0]
|
|
400
|
-
end
|
|
401
|
-
unique
|
|
402
|
-
end
|
|
403
|
-
|
|
404
480
|
def get_sorted_non_id_items(items)
|
|
405
481
|
sorted_items = []
|
|
406
482
|
items.each do |item|
|
|
@@ -445,10 +521,15 @@ module OpenC3
|
|
|
445
521
|
packet.sorted_items.each do |item|
|
|
446
522
|
next if item.data_type == :DERIVED
|
|
447
523
|
temp_type = item.id_value ? "Parameter" : type
|
|
448
|
-
|
|
524
|
+
# Only ID items become Parameters in CommandMetaData, where a name shared with
|
|
525
|
+
# a telemetry parameter is a real collision and gets a CMD_ prefix. Arguments
|
|
526
|
+
# are scoped to their MetaCommand and are declared unprefixed, so prefixing the
|
|
527
|
+
# reference here would point at an Argument that doesn't exist.
|
|
528
|
+
prefix = (temp_type == "Parameter" && cmd_vs_tlm == :COMMAND && unique_tlm_params.include?(item.name)) ? "CMD_" : ""
|
|
449
529
|
if item.array_size
|
|
450
|
-
#
|
|
451
|
-
|
|
530
|
+
# XTCE 1.2 defines dedicated argumentRef/parameterRef attributes for
|
|
531
|
+
# Array{Argument,Parameter}RefEntry, so derive the reference from the type.
|
|
532
|
+
reference_symbol = "#{temp_type.downcase}Ref".to_sym
|
|
452
533
|
xml['xtce'].public_send("Array#{temp_type}RefEntry".intern, reference_symbol => prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR)) do
|
|
453
534
|
set_fixed_value(xml, item) if !packed
|
|
454
535
|
xml['xtce'].DimensionList do
|
|
@@ -457,7 +538,7 @@ module OpenC3
|
|
|
457
538
|
xml['xtce'].FixedValue(0)
|
|
458
539
|
end
|
|
459
540
|
xml['xtce'].EndingIndex do
|
|
460
|
-
xml['xtce'].FixedValue((item
|
|
541
|
+
xml['xtce'].FixedValue(array_ending_index(item))
|
|
461
542
|
end
|
|
462
543
|
end
|
|
463
544
|
end
|
|
@@ -520,7 +601,9 @@ module OpenC3
|
|
|
520
601
|
end # StartingIndex
|
|
521
602
|
xml['xtce'].EndingIndex do
|
|
522
603
|
xml['xtce'].FixedValue do
|
|
523
|
-
|
|
604
|
+
# OpenC3 only supports one-dimensional arrays, which is the single
|
|
605
|
+
# Dimension above. The indices give that dimension's length.
|
|
606
|
+
xml['xtce'].text array_ending_index(item)
|
|
524
607
|
end # FixedValue
|
|
525
608
|
end # EndingIndex
|
|
526
609
|
end # Dimension
|
|
@@ -611,16 +694,10 @@ module OpenC3
|
|
|
611
694
|
xml['xtce'].IntegerDataEncoding(:sizeInBits => item.bit_size, :encoding => encoding)
|
|
612
695
|
end
|
|
613
696
|
end
|
|
697
|
+
# ValidRange comes from IntegerDataType and DefaultAlarm from
|
|
698
|
+
# IntegerParameterType, which extends it, so ValidRange must be emitted first.
|
|
699
|
+
to_xtce_valid_range(item, param_or_arg, xml)
|
|
614
700
|
to_xtce_limits(item, xml)
|
|
615
|
-
if item.range and item.range.last <= MAX_64_BIT_INT
|
|
616
|
-
if param_or_arg == "Parameter"
|
|
617
|
-
xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
|
|
618
|
-
else
|
|
619
|
-
xml['xtce'].ValidRangeSet do
|
|
620
|
-
xml['xtce'].ValidRange(:minInclusive => item.range.first, :maxInclusive => item.range.last)
|
|
621
|
-
end
|
|
622
|
-
end
|
|
623
|
-
end
|
|
624
701
|
end # Type
|
|
625
702
|
end # if item.states
|
|
626
703
|
end
|
|
@@ -648,15 +725,35 @@ module OpenC3
|
|
|
648
725
|
xml['xtce'].FloatDataEncoding(:sizeInBits => item.bit_size, :encoding => 'IEEE754_1985')
|
|
649
726
|
end
|
|
650
727
|
end
|
|
728
|
+
# ValidRange comes from FloatDataType and DefaultAlarm from FloatParameterType,
|
|
729
|
+
# which extends it, so ValidRange must be emitted first.
|
|
730
|
+
to_xtce_valid_range(item, param_or_arg, xml)
|
|
651
731
|
to_xtce_limits(item, xml)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Emit the item minimum / maximum as an XTCE ValidRange. Arguments wrap it in a
|
|
736
|
+
# ValidRangeSet; parameters use a bare ValidRange.
|
|
737
|
+
def to_xtce_valid_range(item, param_or_arg, xml)
|
|
738
|
+
return unless item.range
|
|
739
|
+
|
|
740
|
+
minimum = item.range.first
|
|
741
|
+
maximum = item.range.last
|
|
742
|
+
if item.data_type == :INT or item.data_type == :UINT
|
|
743
|
+
# IntegerRangeType is xs:long, so a wider range (a full 64 bit UINT, for
|
|
744
|
+
# example) can't be expressed and is dropped rather than emitted invalid.
|
|
745
|
+
return if minimum < XS_LONG_MIN or maximum > XS_LONG_MAX
|
|
746
|
+
else
|
|
747
|
+
# FloatRangeType is xs:double, which covers any finite float. Infinity and NaN
|
|
748
|
+
# have no valid xs:double lexical form here, so skip them.
|
|
749
|
+
return unless minimum.finite? and maximum.finite?
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
if param_or_arg == "Parameter"
|
|
753
|
+
xml['xtce'].ValidRange(:minInclusive => minimum, :maxInclusive => maximum)
|
|
754
|
+
else
|
|
755
|
+
xml['xtce'].ValidRangeSet do
|
|
756
|
+
xml['xtce'].ValidRange(:minInclusive => minimum, :maxInclusive => maximum)
|
|
660
757
|
end
|
|
661
758
|
end
|
|
662
759
|
end
|
|
@@ -664,20 +761,11 @@ module OpenC3
|
|
|
664
761
|
def to_xtce_string(item, param_or_arg, xml, string_or_binary, prefix: "")
|
|
665
762
|
attrs = { :name => (prefix + item.name.tr(INVALID_CHARS, REPLACEMENT_CHAR) + '_Type') }
|
|
666
763
|
attrs[:characterWidth] = 8 if string_or_binary == 'String'
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
attrs[:initialValue] = '0x' + item.default.simple_formatted
|
|
670
|
-
else
|
|
671
|
-
if string_or_binary == 'String'
|
|
672
|
-
attrs[:initialValue] = item.default.inspect
|
|
673
|
-
else
|
|
674
|
-
attrs[:initialValue] = item.default.inspect.unpack('H*').first
|
|
675
|
-
end
|
|
676
|
-
end
|
|
677
|
-
end
|
|
764
|
+
initial_value = get_string_or_block_initial_value(item, string_or_binary)
|
|
765
|
+
attrs[:initialValue] = initial_value if initial_value
|
|
678
766
|
attrs[:shortDescription] = item.description if item.description
|
|
679
767
|
xml['xtce'].public_send(string_or_binary + param_or_arg + 'Type', attrs) do
|
|
680
|
-
#
|
|
768
|
+
# Strings and Blocks don't get a byteOrder
|
|
681
769
|
to_xtce_units(item, xml)
|
|
682
770
|
if string_or_binary == 'String'
|
|
683
771
|
xml['xtce'].StringDataEncoding(:encoding => 'UTF-8') do
|
|
@@ -764,7 +852,9 @@ module OpenC3
|
|
|
764
852
|
xml['xtce'].Alias(:nameSpace => ALIAS_NAMESPACE, :alias => item.name)
|
|
765
853
|
end
|
|
766
854
|
end
|
|
767
|
-
|
|
855
|
+
# The time item is the source of the association, so it doesn't get one -
|
|
856
|
+
# pointing it at itself says a parameter is its own timestamp.
|
|
857
|
+
if has_packet_time && item.name != @packet_time_string
|
|
768
858
|
xml['xtce'].ParameterProperties do
|
|
769
859
|
xml['xtce'].TimeAssociation(:parameterRef => @packet_time_string)
|
|
770
860
|
end
|
|
@@ -783,16 +873,6 @@ module OpenC3
|
|
|
783
873
|
end
|
|
784
874
|
end
|
|
785
875
|
|
|
786
|
-
def to_xtce_endianness(item, xml)
|
|
787
|
-
if item.endianness == :LITTLE_ENDIAN and item.bit_size > 8
|
|
788
|
-
xml['xtce'].ByteOrderList do
|
|
789
|
-
(((item.bit_size - 1) / 8) + 1).times do |byte_significance|
|
|
790
|
-
xml['xtce'].Byte(:byteSignificance => byte_significance)
|
|
791
|
-
end
|
|
792
|
-
end
|
|
793
|
-
end
|
|
794
|
-
end
|
|
795
|
-
|
|
796
876
|
def to_xtce_conversion(item, xml)
|
|
797
877
|
if item.read_conversion
|
|
798
878
|
conversion = item.read_conversion
|
|
@@ -154,7 +154,10 @@ module OpenC3
|
|
|
154
154
|
current_type
|
|
155
155
|
end
|
|
156
156
|
|
|
157
|
-
XTCE_IGNORED_ELEMENTS = ['text', '
|
|
157
|
+
XTCE_IGNORED_ELEMENTS = ['text', 'Alias', 'Header']
|
|
158
|
+
|
|
159
|
+
# Namespace COSMOS writes its own item names into when the XTCE name had to differ
|
|
160
|
+
COSMOS_ALIAS_NAMESPACE = 'COSMOS'
|
|
158
161
|
|
|
159
162
|
def xtce_process_element(element)
|
|
160
163
|
if XTCE_IGNORED_ELEMENTS.include?(element.name)
|
|
@@ -418,6 +421,30 @@ module OpenC3
|
|
|
418
421
|
end
|
|
419
422
|
@arguments[element["name"]] = @current_argument
|
|
420
423
|
|
|
424
|
+
when 'AliasSet'
|
|
425
|
+
# COSMOS records the original item name here whenever it had to write a different
|
|
426
|
+
# one: a name containing characters XTCE forbids, or the CMD_ prefix that keeps a
|
|
427
|
+
# command ID item from colliding with a telemetry parameter of the same name.
|
|
428
|
+
# Restoring it means a name survives an export / import round trip. References
|
|
429
|
+
# elsewhere in the document still use the XTCE name, which is what the parameter
|
|
430
|
+
# and argument lookups are keyed by, so nothing else has to change.
|
|
431
|
+
cosmos_alias = element.elements.find do |child|
|
|
432
|
+
child.name == 'Alias' && child['nameSpace'] == COSMOS_ALIAS_NAMESPACE
|
|
433
|
+
end
|
|
434
|
+
if cosmos_alias and cosmos_alias['alias']
|
|
435
|
+
case element.parent&.name
|
|
436
|
+
when 'Parameter'
|
|
437
|
+
@current_parameter&.cosmos_name = cosmos_alias['alias']
|
|
438
|
+
when 'Argument'
|
|
439
|
+
@current_argument&.cosmos_name = cosmos_alias['alias']
|
|
440
|
+
else
|
|
441
|
+
# Do Nothing - COSMOS also writes aliases on SequenceContainer, MetaCommand
|
|
442
|
+
# and SpaceSystem to carry the original packet and target names. Those are
|
|
443
|
+
# not restored: a packet is registered under its name as soon as it is
|
|
444
|
+
# created, so renaming it here would leave the registration disagreeing.
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
421
448
|
when 'ParameterProperties'
|
|
422
449
|
element.attributes.each do |att_name, att|
|
|
423
450
|
@current_parameter[att.name] = att.value
|
|
@@ -452,8 +479,10 @@ module OpenC3
|
|
|
452
479
|
# Handled in MetaCommand
|
|
453
480
|
|
|
454
481
|
when 'Comparison'
|
|
455
|
-
# Need to set ID value for item
|
|
456
|
-
item
|
|
482
|
+
# Need to set ID value for item. The reference is the XTCE name, which is not the
|
|
483
|
+
# item's name when an alias restored the original.
|
|
484
|
+
parameter = @parameters[element['parameterRef']]
|
|
485
|
+
item = @current_packet.get_item(parameter ? cosmos_item_name(parameter) : element['parameterRef'])
|
|
457
486
|
item.id_value = Integer(element['value'])
|
|
458
487
|
if @current_cmd_or_tlm == PacketConfig::COMMAND
|
|
459
488
|
item.default = item.id_value
|
|
@@ -476,8 +505,9 @@ module OpenC3
|
|
|
476
505
|
xtce_handle_base_container('BaseContainer', element)
|
|
477
506
|
|
|
478
507
|
when 'ArgumentAssignment'
|
|
479
|
-
# Need to set ID value for item
|
|
480
|
-
|
|
508
|
+
# Need to set ID value for item, by the item's name rather than the XTCE name
|
|
509
|
+
argument = @arguments[element['argumentName']]
|
|
510
|
+
item = @current_packet.get_item(argument ? cosmos_item_name(argument) : element['argumentName'])
|
|
481
511
|
value = element['argumentValue']
|
|
482
512
|
if item.states && item.states[value.to_s.upcase]
|
|
483
513
|
item.id_value = item.states[value.to_s.upcase]
|
|
@@ -496,9 +526,16 @@ module OpenC3
|
|
|
496
526
|
return true # Recurse further
|
|
497
527
|
end
|
|
498
528
|
|
|
529
|
+
# The COSMOS name of a parameter / argument: its alias when the XTCE name had to
|
|
530
|
+
# differ from it, otherwise the XTCE name itself
|
|
531
|
+
def cosmos_item_name(object)
|
|
532
|
+
object.cosmos_name || object.name
|
|
533
|
+
end
|
|
534
|
+
|
|
499
535
|
def process_ref_entry(element)
|
|
500
536
|
reference_location, bit_offset = xtce_handle_location_in_container_in_bits(element)
|
|
501
537
|
object, type, data_type, array_type = get_object_types(element)
|
|
538
|
+
item_name = cosmos_item_name(object)
|
|
502
539
|
bit_size = Integer(type.sizeInBits)
|
|
503
540
|
if array_type
|
|
504
541
|
array_bit_size = process_array_type(element, bit_size)
|
|
@@ -509,16 +546,18 @@ module OpenC3
|
|
|
509
546
|
if bit_offset
|
|
510
547
|
case reference_location
|
|
511
548
|
when 'containerStart'
|
|
512
|
-
item = @current_packet.define_item(
|
|
549
|
+
item = @current_packet.define_item(item_name, bit_offset, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
|
|
513
550
|
when 'containerEnd'
|
|
514
|
-
item = @current_packet.define_item(
|
|
551
|
+
item = @current_packet.define_item(item_name, -bit_offset, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
|
|
515
552
|
when 'previousEntry', nil
|
|
516
|
-
|
|
553
|
+
# Packet#length is bytes while the location is bits, so the offset is measured
|
|
554
|
+
# from the end of what has been defined so far in bits
|
|
555
|
+
item = @current_packet.define_item(item_name, (@current_packet.length * 8) + bit_offset, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
|
|
517
556
|
when 'nextEntry'
|
|
518
557
|
raise 'nextEntry is not supported'
|
|
519
558
|
end
|
|
520
559
|
else
|
|
521
|
-
item = @current_packet.append_item(
|
|
560
|
+
item = @current_packet.append_item(item_name, bit_size, data_type, array_bit_size, type.endianness) # overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
|
|
522
561
|
end
|
|
523
562
|
|
|
524
563
|
item.description = type.shortDescription if type.shortDescription
|
|
@@ -534,15 +573,15 @@ module OpenC3
|
|
|
534
573
|
if /Parameter/.match?(element.name)
|
|
535
574
|
# Look up the parameter and parameter type
|
|
536
575
|
parameter = @parameters[element['parameterRef']]
|
|
537
|
-
raise "parameterRef #{element['parameterRef']} not found" unless parameter
|
|
576
|
+
raise "parameterRef #{element['parameterRef']} not found (parameter lookup)" unless parameter
|
|
538
577
|
|
|
539
578
|
parameter_type = @parameter_types[parameter.parameterTypeRef]
|
|
540
|
-
raise "parameterTypeRef #{parameter.parameterTypeRef} not found" unless parameter_type
|
|
579
|
+
raise "parameterTypeRef #{parameter.parameterTypeRef} not found (parameter type lookup)" unless parameter_type
|
|
541
580
|
|
|
542
581
|
if element.name == 'ArrayParameterRefEntry'
|
|
543
582
|
array_type = parameter_type
|
|
544
583
|
parameter_type = @parameter_types[array_type.arrayTypeRef]
|
|
545
|
-
raise "arrayTypeRef #{parameter.arrayTypeRef} not found" unless parameter_type
|
|
584
|
+
raise "arrayTypeRef #{parameter.arrayTypeRef} not found (array parameter type)" unless parameter_type
|
|
546
585
|
end
|
|
547
586
|
refName = 'parameterRef'
|
|
548
587
|
object = parameter
|
|
@@ -550,24 +589,25 @@ module OpenC3
|
|
|
550
589
|
else
|
|
551
590
|
# Look up the argument and argument type
|
|
552
591
|
if element.name == 'ArrayArgumentRefEntry'
|
|
553
|
-
#
|
|
554
|
-
|
|
555
|
-
|
|
592
|
+
# XTCE 1.2 requires argumentRef on ArrayArgumentRefEntry. Older XTCE
|
|
593
|
+
# (1.0/1.1) and files exported by prior COSMOS versions used parameterRef
|
|
594
|
+
# here (a defect in those schemas), so accept it as a fallback.
|
|
595
|
+
refName = element['argumentRef'] ? 'argumentRef' : 'parameterRef'
|
|
596
|
+
argument = @arguments[element[refName]]
|
|
597
|
+
raise "#{refName} #{element[refName]} not found (array argument lookup)" unless argument
|
|
556
598
|
|
|
557
599
|
argument_type = @argument_types[argument.argumentTypeRef]
|
|
558
|
-
raise "argumentTypeRef #{argument.argumentTypeRef} not found" unless argument_type
|
|
600
|
+
raise "argumentTypeRef #{argument.argumentTypeRef} not found (array argument type lookup)" unless argument_type
|
|
559
601
|
|
|
560
602
|
array_type = argument_type
|
|
561
603
|
argument_type = @argument_types[array_type.arrayTypeRef]
|
|
562
|
-
raise "arrayTypeRef #{array_type.arrayTypeRef} not found" unless argument_type
|
|
563
|
-
|
|
564
|
-
refName = 'parameterRef'
|
|
604
|
+
raise "arrayTypeRef #{array_type.arrayTypeRef} not found (array argument element type)" unless argument_type
|
|
565
605
|
else
|
|
566
606
|
argument = @arguments[element['argumentRef']]
|
|
567
|
-
raise "argumentRef #{element['argumentRef']} not found" unless argument
|
|
607
|
+
raise "argumentRef #{element['argumentRef']} not found (argument lookup)" unless argument
|
|
568
608
|
|
|
569
609
|
argument_type = @argument_types[argument.argumentTypeRef]
|
|
570
|
-
raise "argumentTypeRef #{argument.argumentTypeRef} not found" unless argument_type
|
|
610
|
+
raise "argumentTypeRef #{argument.argumentTypeRef} not found (argument type lookup)" unless argument_type
|
|
571
611
|
|
|
572
612
|
refName = 'argumentRef'
|
|
573
613
|
end
|
|
@@ -692,10 +732,18 @@ module OpenC3
|
|
|
692
732
|
item.default = []
|
|
693
733
|
else
|
|
694
734
|
if type.initialValue
|
|
695
|
-
if
|
|
735
|
+
if data_type == :BLOCK
|
|
736
|
+
# Binary initialValue is xs:hexBinary (raw hex, optional 0x prefix)
|
|
737
|
+
begin
|
|
738
|
+
item.default = type.initialValue.hex_to_byte_string
|
|
739
|
+
rescue ArgumentError, TypeError => e
|
|
740
|
+
raise "#{item.name} initialValue '#{type.initialValue}' is not valid hexBinary (#{e.message})"
|
|
741
|
+
end
|
|
742
|
+
elsif type.initialValue.upcase.start_with?("0X")
|
|
696
743
|
item.default = type.initialValue.hex_to_byte_string
|
|
697
744
|
else
|
|
698
|
-
# Strip quotes
|
|
745
|
+
# Strip the quotes COSMOS 7.2 and earlier wrote around string defaults.
|
|
746
|
+
# Current exports write the value unquoted.
|
|
699
747
|
if type.initialValue[0] == '"' && type.initialValue[-1] == '"'
|
|
700
748
|
item.default = type.initialValue[1..-2]
|
|
701
749
|
else
|