openc3 6.6.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +66 -10
  3. data/data/config/command_modifiers.yaml +3 -3
  4. data/data/config/interface_modifiers.yaml +1 -1
  5. data/data/config/table_manager.yaml +1 -1
  6. data/data/config/telemetry_modifiers.yaml +3 -3
  7. data/data/config/widgets.yaml +2 -2
  8. data/lib/openc3/accessors.rb +1 -1
  9. data/lib/openc3/api/settings_api.rb +8 -0
  10. data/lib/openc3/api/stash_api.rb +1 -1
  11. data/lib/openc3/api/tlm_api.rb +93 -14
  12. data/lib/openc3/core_ext/kernel.rb +3 -3
  13. data/lib/openc3/logs/log_writer.rb +16 -12
  14. data/lib/openc3/microservices/plugin_microservice.rb +2 -2
  15. data/lib/openc3/models/cvt_model.rb +140 -3
  16. data/lib/openc3/models/plugin_model.rb +7 -2
  17. data/lib/openc3/models/plugin_store_model.rb +70 -0
  18. data/lib/openc3/models/target_model.rb +26 -0
  19. data/lib/openc3/models/tool_model.rb +1 -1
  20. data/lib/openc3/packets/packet.rb +3 -3
  21. data/lib/openc3/packets/parsers/state_parser.rb +7 -1
  22. data/lib/openc3/packets/structure.rb +9 -2
  23. data/lib/openc3/script/calendar.rb +10 -10
  24. data/lib/openc3/script/commands.rb +1 -1
  25. data/lib/openc3/script/script_runner.rb +7 -2
  26. data/lib/openc3/script/tables.rb +3 -3
  27. data/lib/openc3/utilities/authorization.rb +1 -1
  28. data/lib/openc3/utilities/cosmos_rails_formatter.rb +1 -1
  29. data/lib/openc3/utilities/logger.rb +1 -1
  30. data/lib/openc3/utilities/running_script.rb +5 -1
  31. data/lib/openc3/version.rb +5 -5
  32. data/templates/tool_angular/package.json +2 -2
  33. data/templates/tool_react/package.json +1 -1
  34. data/templates/tool_svelte/package.json +1 -1
  35. data/templates/tool_vue/package.json +3 -3
  36. data/templates/widget/package.json +2 -2
  37. metadata +59 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d08bcc3b92811101e2096aa443b5459835d0396aba80b1067c8147388ec050e6
4
- data.tar.gz: 2820795b23e3da0530d0306ee2665d69bb5ad86f494609ced199f985929c56f2
3
+ metadata.gz: 35ed91c6cd987b0a1c139ef5cd9670d204eb524ae7eebdf971d58aef3dbc5faa
4
+ data.tar.gz: 1a0d8ca81f317a7a5227d56be83ab847bed1a7c4ac48ea991f34ed46a3e80e1d
5
5
  SHA512:
6
- metadata.gz: 181ce42f3c55f78ee8e6adf3cc7145326974c8239a82478b501def91314169cacb62a9e53c4d0d760e0b0125a1b77e437127f7992d7e4fe42838035640b87dbe
7
- data.tar.gz: 957334977c15e7d8e3fe49d14bec6c2e11c7cba09bbaea70cefe3f85a408255f1e7f2620b642c6f7a137d6ff9bdc1b4b49007fca58a9c6c5f9562b81daa0263e
6
+ metadata.gz: f305acecd79bfd96b93829a7a3b67b4095ef45104fa7571f5d3c59ac70e753668f0a8e5bde7ad2818c3c22966bf55b524e1fa6bb41a14aa124a2c1f063115781
7
+ data.tar.gz: 85f4fc3479388969c39cdedcd180498cf1eeb3870d0c609590f4fb6c816d25efdbc02aff4cf499a96da88b3176846a6ba7e08e81dca483659928c475f673be36
data/bin/openc3cli CHANGED
@@ -63,8 +63,9 @@ def print_usage
63
63
  puts " cli rake # Runs rake in the local directory"
64
64
  puts " cli irb # Runs irb in the local directory"
65
65
  puts " cli script # Interact with scripts. Run with --help for more info."
66
- puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file"
67
- puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file"
66
+ puts " cli validate /PATH/FILENAME.gem SCOPE variables.json # Validate a COSMOS plugin gem file"
67
+ puts " cli load /PATH/FILENAME.gem SCOPE plugin_hash.json # Loads a COSMOS plugin gem file"
68
+ puts " OPTIONS: --variables lets you pass a path to a JSON file containing your plugin's variables"
68
69
  puts " cli list <SCOPE> # Lists installed plugins, SCOPE is DEFAULT if not given"
69
70
  puts " cli generate TYPE OPTIONS # Generate various COSMOS entities"
70
71
  puts " OPTIONS: --ruby or --python is required to specify the language in the generated code unless OPENC3_LANGUAGE is set"
@@ -333,7 +334,7 @@ end
333
334
  # Pass true as the last argument to force install even if a plugin with
334
335
  # the same version number exists
335
336
  #
336
- def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false)
337
+ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false, variables_file: nil)
337
338
  scope ||= 'DEFAULT'
338
339
  check_environment()
339
340
  if $openc3_in_cluster
@@ -352,9 +353,10 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false)
352
353
  end
353
354
 
354
355
  begin
356
+ existing_variables = JSON.parse(File.read(variables_file)) if variables_file
355
357
  if plugin_hash_file
356
358
  # Admin Create / Edit / or Upgrade Plugin
357
- OpenC3::PluginModel.install_phase1(plugin_file_path, scope: scope)
359
+ OpenC3::PluginModel.install_phase1(plugin_file_path, existing_variables: existing_variables, scope: scope)
358
360
  plugin_hash = JSON.parse(File.read(plugin_hash_file), :allow_nan => true, :create_additions => true)
359
361
  else
360
362
  # Init or Command Line openc3cli load with no plugin_hash_file
@@ -378,7 +380,7 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false)
378
380
  end
379
381
  return if found
380
382
 
381
- plugin_hash = OpenC3::PluginModel.install_phase1(plugin_file_path, scope: scope)
383
+ plugin_hash = OpenC3::PluginModel.install_phase1(plugin_file_path, existing_variables: existing_variables, scope: scope)
382
384
  end
383
385
 
384
386
  # Determine if plugin named in plugin_hash exists
@@ -640,12 +642,39 @@ def cli_script_list(args, options)
640
642
  return 0
641
643
  end
642
644
 
645
+ def parse_suite_runner_options(options)
646
+ suite_runner = nil
647
+ # suite must be given to enable Suite Runner execution
648
+ if options[:suite]
649
+ suite_runner = {}
650
+ suite_runner['suite'] = options[:suite]
651
+ if options[:group]
652
+ suite_runner['group'] = options[:group]
653
+ # script requires group to be set
654
+ if options[:script]
655
+ suite_runner['script'] = options[:script]
656
+ end
657
+ end
658
+ if options[:method]
659
+ suite_runner['method'] = options[:method]
660
+ else
661
+ suite_runner['method'] = 'start'
662
+ end
663
+ if options[:options]
664
+ suite_runner['options'] = options[:options].split(',')
665
+ else
666
+ suite_runner['options'] = ["continueAfterError"]
667
+ end
668
+ end
669
+ return suite_runner
670
+ end
671
+
643
672
  def cli_script_run(args, options)
644
673
  environment = get_env_from_args(args)
645
-
674
+ suite_runner = parse_suite_runner_options(options)
646
675
  ret_code = ERROR_CODE
647
676
  require 'openc3/script'
648
- if (id = script_run(args[1], disconnect: options[:disconnect], environment: environment, scope: options[:scope]))
677
+ if (id = script_run(args[1], disconnect: options[:disconnect], environment: environment, suite_runner: suite_runner, scope: options[:scope]))
649
678
  puts id
650
679
  $script_interrupt_text = " Script #{args[1]} still running remotely.\n" # for Ctrl-C
651
680
  if (options[:wait] < 1) then
@@ -673,10 +702,10 @@ end
673
702
 
674
703
  def cli_script_spawn(args, options)
675
704
  environment = get_env_from_args(args)
676
-
705
+ suite_runner = parse_suite_runner_options(options)
677
706
  ret_code = ERROR_CODE
678
707
  require 'openc3/script'
679
- if (id = script_run(args[1], disconnect: options[:disconnect], environment: environment, scope: options[:scope]))
708
+ if (id = script_run(args[1], disconnect: options[:disconnect], environment: environment, suite_runner: suite_runner, scope: options[:scope]))
680
709
  puts id
681
710
  ret_code = 0
682
711
  end
@@ -769,6 +798,22 @@ def cli_script(args=[])
769
798
  opts.on("--scope SCOPE", "Run with specified scope (default = DEFAULT)") do |arg|
770
799
  options[:scope] = arg
771
800
  end
801
+ opts.on("--suite SUITE", "Run with specified suite") do |arg|
802
+ options[:suite] = arg
803
+ end
804
+ opts.on("--group GROUP", "Run with specified group") do |arg|
805
+ options[:group] = arg
806
+ end
807
+ opts.on("--script SCRIPT", "Run with specified script") do |arg|
808
+ options[:script] = arg
809
+ end
810
+ opts.on("--method start", "Method must be start, setup, or teardown. Default is start.") do |arg|
811
+ options[:method] = arg
812
+ end
813
+ # TODO 7.0: Should these all be snake case?
814
+ opts.on("--options options", "Options is a comma separated list consisting of continueAfterError,pauseOnError,abortAfterError,manual,loop,breakLoopOnError. Default is continueAfterError.") do |arg|
815
+ options[:options] = arg
816
+ end
772
817
  opts.on("-d", "--disconnect", "Run a script in disconnect mode (default = false)") do |arg|
773
818
  options[:disconnect] = arg
774
819
  end
@@ -848,7 +893,18 @@ if not ARGV[0].nil? # argument(s) given
848
893
  when 'load'
849
894
  # force is a boolean so if they pass 'force' it is true
850
895
  # See plugins_controller.rb install for usage
851
- load_plugin(ARGV[1], scope: ARGV[2], plugin_hash_file: ARGV[3], force: ARGV[4] == 'force')
896
+ variables_option = ARGV.find_index('--variables')
897
+ if variables_option.nil?
898
+ scope = ARGV[2]
899
+ plugin_hash_file = ARGV[3]
900
+ force = ARGV[4] == 'force'
901
+ else
902
+ scope = ARGV[2] unless variables_option <= 2
903
+ plugin_hash_file = ARGV[3] unless variables_option <= 3
904
+ force = ARGV[4] == 'force' unless variables_option <= 4
905
+ variables_file = ARGV[variables_option + 1]
906
+ end
907
+ load_plugin(ARGV[1], scope: scope, plugin_hash_file: plugin_hash_file, force: force, variables_file: variables_file)
852
908
 
853
909
  when 'list'
854
910
  list_plugins(scope: ARGV[1])
@@ -11,7 +11,7 @@ PARAMETER:
11
11
  - name: Bit Offset
12
12
  required: true
13
13
  description: Bit offset into the command packet of the Most Significant Bit of this parameter.
14
- May be negative to indicate on offset from the end of the packet.
14
+ May be negative to indicate an offset from the end of the packet.
15
15
  Always use a bit offset of 0 for derived parameters.
16
16
  values: '[-]?\d+'
17
17
  <%= MetaConfigParser.load('_params.yaml').to_meta_config_yaml(4) %>
@@ -50,7 +50,7 @@ ID_PARAMETER:
50
50
  - name: Bit Offset
51
51
  required: true
52
52
  description: Bit offset into the command packet of the Most Significant Bit of this parameter.
53
- May be negative to indicate on offset from the end of the packet.
53
+ May be negative to indicate an offset from the end of the packet.
54
54
  values: '[-]?\d+'
55
55
  <%= MetaConfigParser.load('_id_params.yaml').to_meta_config_yaml(4) %>
56
56
  example: ID_PARAMETER OPCODE 32 32 UINT 2 2 2 "Opcode identifier"
@@ -80,7 +80,7 @@ ARRAY_PARAMETER:
80
80
  - name: Bit Offset
81
81
  required: true
82
82
  description: Bit offset into the command packet of the Most Significant Bit of this parameter.
83
- May be negative to indicate on offset from the end of the packet.
83
+ May be negative to indicate an offset from the end of the packet.
84
84
  Always use a bit offset of 0 for derived parameters.
85
85
  values: '[-]?\d+'
86
86
  <%= MetaConfigParser.load('_array_params.yaml').to_meta_config_yaml(4) %>
@@ -124,7 +124,7 @@ PROTOCOL:
124
124
  python_example: |
125
125
  INTERFACE DATA_INT openc3/interfaces/tcpip_client_interface.py host.docker.internal 8080 8081 10.0 nil BURST
126
126
  MAP_TARGET DATA
127
- PROTOCOL READ IgnorePacketProtocol INST IMAGE # Drop all INST IMAGE packets
127
+ PROTOCOL READ openc3/interfaces/protocols/ignore_packet_protocol.py INST IMAGE # Drop all INST IMAGE packets
128
128
  OPTION:
129
129
  summary: Set a parameter on an interface
130
130
  description:
@@ -22,7 +22,7 @@ TABLE:
22
22
  - name: Bit Offset
23
23
  required: true
24
24
  description: Bit offset into the table of the Most Significant Bit of this parameter.
25
- May be negative to indicate on offset from the end of the table.
25
+ May be negative to indicate an offset from the end of the table.
26
26
  Always use a bit offset of 0 for derived parameters.
27
27
  values: '[-]?\d+'
28
28
  <%= MetaConfigParser.load('_params.yaml').to_meta_config_yaml(8) %>
@@ -14,7 +14,7 @@ ITEM:
14
14
  - name: Bit Offset
15
15
  required: true
16
16
  description: Bit offset into the telemetry packet of the Most Significant Bit of this item.
17
- May be negative to indicate on offset from the end of the packet.
17
+ May be negative to indicate an offset from the end of the packet.
18
18
  Always use a bit offset of 0 for derived item.
19
19
  values: '[-]?\d+'
20
20
  <%= MetaConfigParser.load('_items.yaml').to_meta_config_yaml(4) %>
@@ -44,7 +44,7 @@ ID_ITEM:
44
44
  - name: Bit Offset
45
45
  required: true
46
46
  description: Bit offset into the telemetry packet of the Most Significant Bit of this item.
47
- May be negative to indicate on offset from the end of the packet.
47
+ May be negative to indicate an offset from the end of the packet.
48
48
  values: '[-]?\d+'
49
49
  <%= MetaConfigParser.load('_id_items.yaml').to_meta_config_yaml(4) %>
50
50
  APPEND_ID_ITEM:
@@ -71,7 +71,7 @@ ARRAY_ITEM:
71
71
  - name: Bit Offset
72
72
  required: true
73
73
  description: Bit offset into the telemetry packet of the Most Significant Bit of this item.
74
- May be negative to indicate on offset from the end of the packet.
74
+ May be negative to indicate an offset from the end of the packet.
75
75
  Always use a bit offset of 0 for derived item.
76
76
  values: '[-]?\d+'
77
77
  <%= MetaConfigParser.load('_array_params.yaml').to_meta_config_yaml(4) %>
@@ -746,11 +746,11 @@ Telemetry Widgets:
746
746
  values: <%= %w(RAW CONVERTED FORMATTED WITH_UNITS) %>
747
747
  - name: Width
748
748
  required: false
749
- description: Width of the LED circle (default = 15)
749
+ description: Width of the LED circle (default = 20)
750
750
  values: .*
751
751
  - name: Height
752
752
  required: false
753
- description: Height of the LED circle (default = 15)
753
+ description: Height of the LED circle (default = 20)
754
754
  values: .*
755
755
  example: |
756
756
  LED INST PARAMS VALUE5 RAW 25 20 # Ellipse
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2022 OpenC3, Inc.
3
+ # Copyright 2024 OpenC3, Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # This program is free software; you can modify and/or redistribute it
@@ -31,6 +31,7 @@ rescue LoadError
31
31
  end
32
32
  require 'openc3/models/setting_model'
33
33
  require 'openc3/models/news_model'
34
+ require 'openc3/models/plugin_store_model'
34
35
 
35
36
  module OpenC3
36
37
  module Api
@@ -43,6 +44,7 @@ module OpenC3
43
44
  'set_setting',
44
45
  'save_setting', # DEPRECATED
45
46
  'update_news',
47
+ 'update_plugin_store',
46
48
  ])
47
49
 
48
50
  def list_settings(manual: false, scope: $openc3_scope, token: $openc3_token)
@@ -103,5 +105,11 @@ module OpenC3
103
105
  rescue Exception => e
104
106
  NewsModel.news_error("Error contacting OpenC3 news feed. #{e.message})")
105
107
  end
108
+
109
+ # Update the local copy of the plugin store data
110
+ def update_plugin_store(manual: false, scope: $openc3_scope, token: $openc3_token)
111
+ authorize(permission: 'admin', manual: manual, scope: scope, token: token)
112
+ PluginStoreModel.update()
113
+ end
106
114
  end
107
115
  end
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2022 OpenC3, Inc
3
+ # Copyright 2024 OpenC3, Inc
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # This program is free software; you can modify and/or redistribute it
@@ -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
@@ -22,7 +22,7 @@
22
22
  #
23
23
  # A portion of this file was funded by Blue Origin Enterprises, L.P.
24
24
  # See https://github.com/OpenC3/cosmos/pull/1963
25
-
25
+ #
26
26
  # A portion of this file was funded by Blue Origin Enterprises, L.P.
27
27
  # See https://github.com/OpenC3/cosmos/pull/1957
28
28
 
@@ -49,6 +49,7 @@ module OpenC3
49
49
  'normalize_tlm',
50
50
  'get_tlm_buffer',
51
51
  'get_tlm_packet',
52
+ 'get_tlm_available',
52
53
  'get_tlm_values',
53
54
  'get_all_tlm',
54
55
  'get_all_telemetry', # DEPRECATED
@@ -254,6 +255,78 @@ module OpenC3
254
255
  items.zip(current_values).map { | item , values | [item, values[0], values[1]]}
255
256
  end
256
257
 
258
+ # Returns the available items from a list of requested screen items
259
+ # This does the packet introspection to determine what is actually available
260
+ # Like if you ask for WITH_UNITS but only RAW is available
261
+ def get_tlm_available(items, manual: false, scope: $openc3_scope, token: $openc3_token)
262
+ results = []
263
+ items.each do |item|
264
+ item_upcase = item.to_s.upcase
265
+ target_name, orig_packet_name, item_name, value_type = item_upcase.split('__')
266
+ packet_name = orig_packet_name
267
+ raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
268
+ if orig_packet_name == 'LATEST'
269
+ # TODO: Do we need to lookup ALL the possible packets for this item?
270
+ # We can have a large cache_timeout of 1 because all we're trying to do is lookup a packet
271
+ packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: 1, scope: scope)
272
+ end
273
+ authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
274
+ begin
275
+ item = TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
276
+ if Packet::RESERVED_ITEM_NAMES.include?(item_name)
277
+ value_type = 'RAW' # Must request the raw value when dealing with the reserved items
278
+ end
279
+
280
+ # QuestDB 9.0.0 only supports DOUBLE arrays: https://questdb.com/docs/concept/array/
281
+ if item['array_size']
282
+ # TODO: This needs work ... we're JSON encoding non numeric array values
283
+ if item['data_type'] == 'STRING' or item['data_type'] == 'BLOCK'
284
+ results << nil
285
+ next
286
+ end
287
+ value_type = 'RAW'
288
+ end
289
+
290
+ case value_type
291
+ when 'WITH_UNITS'
292
+ if item['units']
293
+ results << [target_name, orig_packet_name, item_name, 'WITH_UNITS'].join('__')
294
+ elsif item['format_string']
295
+ results << [target_name, orig_packet_name, item_name, 'FORMATTED'].join('__')
296
+ elsif item['read_conversion'] or item['states']
297
+ results << [target_name, orig_packet_name, item_name, 'CONVERTED'].join('__')
298
+ else
299
+ results << [target_name, orig_packet_name, item_name, 'RAW'].join('__')
300
+ end
301
+ when 'FORMATTED'
302
+ if item['format_string']
303
+ results << [target_name, orig_packet_name, item_name, 'FORMATTED'].join('__')
304
+ elsif item['read_conversion'] or item['states']
305
+ results << [target_name, orig_packet_name, item_name, 'CONVERTED'].join('__')
306
+ else
307
+ results << [target_name, orig_packet_name, item_name, 'RAW'].join('__')
308
+ end
309
+ when 'CONVERTED'
310
+ if item['read_conversion'] or item['states']
311
+ results << [target_name, orig_packet_name, item_name, 'CONVERTED'].join('__')
312
+ else
313
+ results << [target_name, orig_packet_name, item_name, 'RAW'].join('__')
314
+ end
315
+ else # RAW or unknown
316
+ results << [target_name, orig_packet_name, item_name, 'RAW'].join('__')
317
+ end
318
+
319
+ # Tack on __LIMITS to notify that we have an available limits value
320
+ if item['limits']['DEFAULT']
321
+ results[-1] += '__LIMITS'
322
+ end
323
+ rescue RuntimeError => e
324
+ results << nil
325
+ end
326
+ end
327
+ results
328
+ end
329
+
257
330
  # Returns all the item values (along with their limits state). The items
258
331
  # can be from any target and packet and thus must be fully qualified with
259
332
  # their target and packet names.
@@ -264,26 +337,32 @@ module OpenC3
264
337
  # @return [Array<Object, Symbol>]
265
338
  # Array consisting of the item value and limits state
266
339
  # given as symbols such as :RED, :YELLOW, :STALE
267
- def get_tlm_values(items, stale_time: 30, cache_timeout: nil, manual: false, scope: $openc3_scope, token: $openc3_token)
268
- if !items.is_a?(Array) || !items[0].is_a?(String)
340
+ def get_tlm_values(items, stale_time: 30, cache_timeout: nil, manual: false, start_time: nil, end_time: nil, scope: $openc3_scope, token: $openc3_token)
341
+ if !items.is_a?(Array)
269
342
  raise ArgumentError, "items must be array of strings: ['TGT__PKT__ITEM__TYPE', ...]"
270
343
  end
271
344
  packets = []
272
345
  cvt_items = []
273
346
  items.each_with_index do |item, index|
274
- item_upcase = item.to_s.upcase
275
- target_name, packet_name, item_name, value_type = item_upcase.split('__')
276
- raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
277
- packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope) if packet_name == 'LATEST'
278
- # Change packet_name in case of LATEST and ensure upcase
279
- cvt_items[index] = [target_name, packet_name, item_name, value_type]
347
+ if item.nil?
348
+ # null items mean that it doesn't exist
349
+ cvt_items[index] = nil
350
+ else
351
+ item_upcase = item.to_s.upcase
352
+ target_name, packet_name, item_name, value_type, limits = item_upcase.split('__')
353
+ raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
354
+ if packet_name == 'LATEST' # Lookup packet_name in case of LATEST
355
+ packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope)
356
+ end
357
+ end
358
+ cvt_items[index] = [target_name, packet_name, item_name, value_type, limits]
280
359
  packets << [target_name, packet_name]
281
360
  end
282
361
  packets.uniq!
283
362
  packets.each do |target_name, packet_name|
284
363
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
285
364
  end
286
- CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, cache_timeout: cache_timeout, scope: scope)
365
+ CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, cache_timeout: cache_timeout, start_time: start_time, end_time: end_time, scope: scope)
287
366
  end
288
367
 
289
368
  # Returns an array of all the telemetry packet hashes
@@ -374,16 +453,16 @@ module OpenC3
374
453
  raise ArgumentError, "packets must be nested array: [['TGT','PKT'],...]"
375
454
  end
376
455
 
377
- result = {}
456
+ results = {}
378
457
  packets.each do |target_name, packet_name|
379
458
  target_name = target_name.upcase
380
459
  packet_name = packet_name.upcase
381
460
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
382
461
  topic = "#{scope}__DECOM__{#{target_name}}__#{packet_name}"
383
462
  id, _ = Topic.get_newest_message(topic)
384
- result[topic] = id ? id : '0-0'
463
+ results[topic] = id ? id : '0-0'
385
464
  end
386
- result.to_a.join(SUBSCRIPTION_DELIMITER)
465
+ results.to_a.join(SUBSCRIPTION_DELIMITER)
387
466
  end
388
467
  # Alias the singular as well since that matches COSMOS 4
389
468
  alias subscribe_packet subscribe_packets
@@ -14,10 +14,10 @@
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
- # This file may also be used under the terms of a commercial license
20
+ # This file may also be used under the terms of a commercial license
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  # OpenC3 specific additions to the Ruby Kernel module
@@ -40,6 +40,6 @@ module Kernel
40
40
  # @param start [Integer] The number of stack entries to skip
41
41
  # @return [Symbol] The name of the calling method
42
42
  def calling_method(start = 1)
43
- caller[start][/`([^']*)'/, 1].intern
43
+ caller[start][/[`']([^']*)'/, 1].intern
44
44
  end
45
45
  end
@@ -319,19 +319,23 @@ module OpenC3
319
319
  begin
320
320
  @file.close unless @file.closed?
321
321
  Logger.debug "Log File Closed : #{@filename}"
322
- date = first_timestamp[0..7] # YYYYMMDD
323
- bucket_key = File.join(@remote_log_directory, date, bucket_filename())
324
- # Cleanup timestamps here so they are unset for the next file
325
- @first_time = nil
326
- @last_time = nil
327
- threads << BucketUtilities.move_log_file_to_bucket(@filename, bucket_key)
328
- # Now that the file is in storage, trim the Redis stream after a delay
329
- @cleanup_offsets << {}
330
- @last_offsets.each do |redis_topic, last_offset|
331
- @cleanup_offsets[-1][redis_topic] = last_offset
322
+ # Only try to moce the file if we've written data to it
323
+ # This is indicated by the first and last timestamps being set
324
+ if @first_time and @last_time
325
+ date = first_timestamp[0..7] # YYYYMMDD
326
+ bucket_key = File.join(@remote_log_directory, date, bucket_filename())
327
+ # Cleanup timestamps here so they are unset for the next file
328
+ @first_time = nil
329
+ @last_time = nil
330
+ threads << BucketUtilities.move_log_file_to_bucket(@filename, bucket_key)
331
+ # Now that the file is in storage, trim the Redis stream after a delay
332
+ @cleanup_offsets << {}
333
+ @last_offsets.each do |redis_topic, last_offset|
334
+ @cleanup_offsets[-1][redis_topic] = last_offset
335
+ end
336
+ @cleanup_times << (Time.now + CLEANUP_DELAY)
337
+ @last_offsets.clear
332
338
  end
333
- @cleanup_times << (Time.now + CLEANUP_DELAY)
334
- @last_offsets.clear
335
339
  rescue Exception => e
336
340
  Logger.error "Error closing #{@filename} : #{e.formatted}"
337
341
  end
@@ -43,8 +43,8 @@ module OpenC3
43
43
  # Fortify: Process Control
44
44
  # This is dangerous! However, plugins need to be able to run whatever they want.
45
45
  # Only admins can install plugins and they need to be vetted for content.
46
- # NOTE: In OpenC3 EE each microservice gets its own container so the potential
47
- # footprint is much smaller. In OpenSource OpenC3 you're in the same container
46
+ # NOTE: In Enterprise each microservice gets its own container so the potential
47
+ # footprint is much smaller. In Core you're in the same container
48
48
  # as all the other plugins.
49
49
  exec(*@config["cmd"])
50
50
  end