openc3 7.0.0.pre.rc3 → 7.0.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/data/config/interface_modifiers.yaml +1 -1
  3. data/data/config/item_modifiers.yaml +18 -6
  4. data/data/config/telemetry.yaml +1 -1
  5. data/lib/openc3/accessors/json_accessor.rb +1 -1
  6. data/lib/openc3/api/tlm_api.rb +3 -3
  7. data/lib/openc3/config/config_parser.rb +4 -4
  8. data/lib/openc3/conversions/conversion.rb +3 -3
  9. data/lib/openc3/core_ext/faraday.rb +4 -0
  10. data/lib/openc3/logs/log_writer.rb +24 -6
  11. data/lib/openc3/logs/packet_log_writer.rb +1 -4
  12. data/lib/openc3/logs/stream_log_pair.rb +11 -4
  13. data/lib/openc3/logs/text_log_writer.rb +1 -4
  14. data/lib/openc3/microservices/interface_microservice.rb +8 -2
  15. data/lib/openc3/microservices/log_microservice.rb +7 -2
  16. data/lib/openc3/microservices/microservice.rb +10 -4
  17. data/lib/openc3/microservices/queue_microservice.rb +3 -0
  18. data/lib/openc3/microservices/scope_cleanup_microservice.rb +116 -1
  19. data/lib/openc3/microservices/text_log_microservice.rb +4 -1
  20. data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +2 -0
  21. data/lib/openc3/models/activity_model.rb +15 -3
  22. data/lib/openc3/models/cvt_model.rb +2 -247
  23. data/lib/openc3/models/plugin_store_model.rb +1 -1
  24. data/lib/openc3/models/script_engine_model.rb +1 -1
  25. data/lib/openc3/models/target_model.rb +32 -34
  26. data/lib/openc3/models/tool_model.rb +18 -5
  27. data/lib/openc3/models/trigger_model.rb +1 -1
  28. data/lib/openc3/models/widget_model.rb +1 -2
  29. data/lib/openc3/operators/operator.rb +9 -7
  30. data/lib/openc3/packets/json_packet.rb +2 -0
  31. data/lib/openc3/packets/packet.rb +1 -0
  32. data/lib/openc3/packets/packet_config.rb +28 -12
  33. data/lib/openc3/script/calendar.rb +8 -0
  34. data/lib/openc3/script/script.rb +19 -0
  35. data/lib/openc3/script/storage.rb +6 -6
  36. data/lib/openc3/system/system.rb +6 -6
  37. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +0 -2
  38. data/lib/openc3/top_level.rb +15 -63
  39. data/lib/openc3/topics/limits_event_topic.rb +1 -1
  40. data/lib/openc3/utilities/bucket_utilities.rb +3 -1
  41. data/lib/openc3/utilities/cli_generator.rb +7 -0
  42. data/lib/openc3/utilities/cmd_log.rb +1 -1
  43. data/lib/openc3/utilities/local_mode.rb +3 -0
  44. data/lib/openc3/utilities/process_manager.rb +1 -1
  45. data/lib/openc3/utilities/python_proxy.rb +11 -4
  46. data/lib/openc3/utilities/questdb_client.rb +735 -19
  47. data/lib/openc3/utilities/running_script.rb +25 -7
  48. data/lib/openc3/utilities/script.rb +452 -0
  49. data/lib/openc3/utilities/secrets.rb +1 -1
  50. data/lib/openc3/version.rb +5 -5
  51. data/templates/conversion/conversion.py +0 -8
  52. data/templates/conversion/conversion.rb +0 -11
  53. data/templates/tool_angular/package.json +2 -2
  54. data/templates/tool_react/package.json +1 -1
  55. data/templates/tool_svelte/package.json +1 -1
  56. data/templates/tool_vue/package.json +3 -3
  57. data/templates/widget/package.json +2 -2
  58. metadata +16 -2
  59. data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +0 -23
@@ -110,7 +110,7 @@ module OpenC3
110
110
  end
111
111
 
112
112
  return _get_storage_file("#{part}/#{path}", scope: scope)
113
- rescue => e
113
+ rescue
114
114
  if part == "targets_modified"
115
115
  part = "targets"
116
116
  redo
@@ -141,13 +141,13 @@ module OpenC3
141
141
  return result['url']
142
142
  end
143
143
 
144
- def _get_storage_file(path, scope: $openc3_scope)
144
+ def _get_storage_file(path, bucket: 'OPENC3_CONFIG_BUCKET', scope: $openc3_scope)
145
145
  # Create Tempfile to store data
146
146
  file = Tempfile.new('target', binmode: true)
147
147
  file.filename = path
148
148
 
149
149
  endpoint = "/openc3-api/storage/download/#{scope}/#{path}"
150
- result = _get_presigned_request(endpoint, scope: scope)
150
+ result = _get_presigned_request(endpoint, bucket: bucket, scope: scope)
151
151
  puts "Reading #{scope}/#{path}"
152
152
 
153
153
  # Try to get the file
@@ -186,11 +186,11 @@ module OpenC3
186
186
  end
187
187
  end
188
188
 
189
- def _get_presigned_request(endpoint, external: nil, scope: $openc3_scope)
189
+ def _get_presigned_request(endpoint, external: nil, bucket: 'OPENC3_CONFIG_BUCKET', scope: $openc3_scope)
190
190
  if external or !$openc3_in_cluster
191
- response = $api_server.request('get', endpoint, query: { bucket: 'OPENC3_CONFIG_BUCKET' }, scope: scope)
191
+ response = $api_server.request('get', endpoint, query: { bucket: bucket }, scope: scope)
192
192
  else
193
- response = $api_server.request('get', endpoint, query: { bucket: 'OPENC3_CONFIG_BUCKET', internal: true }, scope: scope)
193
+ response = $api_server.request('get', endpoint, query: { bucket: bucket, internal: true }, scope: scope)
194
194
  end
195
195
  if response.nil? || response.status != 201
196
196
  raise "Failed to get presigned URL for #{endpoint}"
@@ -81,22 +81,22 @@ module OpenC3
81
81
  # Nothing to do if there are no targets
82
82
  return if target_names.nil? or target_names.length == 0
83
83
  if @@instance.nil?
84
- FileUtils.mkdir_p("#{base_dir}/targets")
84
+ targets_path = "#{base_dir}/_targets"
85
+ FileUtils.mkdir_p(targets_path)
85
86
  bucket = Bucket.getClient()
86
87
  target_names.each do |target_name|
87
88
  # Retrieve bucket/targets/target_name/<TARGET>_current.zip
88
- zip_path = "#{base_dir}/targets/#{target_name}_current.zip"
89
+ zip_path = "#{targets_path}/#{target_name}_current.zip"
89
90
  FileUtils.mkdir_p(File.dirname(zip_path))
90
91
  bucket_key = "#{scope}/target_archives/#{target_name}/#{target_name}_current.zip"
91
92
  Logger.info("Retrieving #{bucket_key} from targets bucket")
92
93
  bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, path: zip_path)
93
- targets_path = "#{base_dir}/targets"
94
- FileUtils.mkdir_p(targets_path)
95
94
  Zip::File.open(zip_path) do |zip_file|
96
95
  zip_file.each do |entry|
97
96
  zip_file.extract(entry.name, destination_directory: targets_path)
98
97
  end
99
98
  end
99
+ FileUtils.rm(zip_path) if File.exist?(zip_path)
100
100
 
101
101
  # Now add any modifications in targets_modified/TARGET/cmd_tlm
102
102
  # This adds support for remembering dynamically created packets
@@ -106,13 +106,13 @@ module OpenC3
106
106
  _, files = bucket.list_files(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: bucket_path)
107
107
  files.each do |file|
108
108
  bucket_key = File.join(bucket_path, file['name'])
109
- local_path = "#{base_dir}/targets/#{target_name}/cmd_tlm/#{file['name']}"
109
+ local_path = "#{targets_path}/#{target_name}/cmd_tlm/#{file['name']}"
110
110
  bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, path: local_path)
111
111
  end
112
112
  end
113
113
 
114
114
  # Build System from targets
115
- System.instance(target_names, "#{base_dir}/targets")
115
+ System.instance(target_names, targets_path)
116
116
  end
117
117
  end
118
118
 
@@ -221,7 +221,6 @@ module OpenC3
221
221
  else
222
222
  Logger.error connect_error.formatted
223
223
  unless @connection_failed_messages.include?(connect_error.message)
224
- OpenC3.write_exception_file(connect_error)
225
224
  @connection_failed_messages << connect_error.message
226
225
  end
227
226
  end
@@ -242,7 +241,6 @@ module OpenC3
242
241
  else
243
242
  Logger.error err.formatted
244
243
  unless @connection_lost_messages.include?(err.message)
245
- OpenC3.write_exception_file(err)
246
244
  @connection_lost_messages << err.message
247
245
  end
248
246
  end
@@ -1,4 +1,4 @@
1
- # encoding: ascii-8bit
1
+ # encoding: utf-8
2
2
 
3
3
  # Copyright 2022 Ball Aerospace & Technologies Corp.
4
4
  # All Rights Reserved.
@@ -101,6 +101,20 @@ module OpenC3
101
101
  end
102
102
  end
103
103
 
104
+ def self.sanitize_path(path)
105
+ return '' if path.nil?
106
+ # path is passed as a parameter thus we have to sanitize it or the code scanner detects:
107
+ # "Uncontrolled data used in path expression"
108
+ # This method is taken directly from the Rails source:
109
+ # https://api.rubyonrails.org/v5.2/classes/ActiveStorage/Filename.html#method-i-sanitized
110
+ # NOTE: I removed the '/' character because we have to allow this in order to traverse the path
111
+ sanitized = path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;\t\r\n\\", "-").gsub('..', '-')
112
+ if sanitized != path
113
+ raise StorageError, "Invalid path: #{path}"
114
+ end
115
+ sanitized
116
+ end
117
+
104
118
  require 'openc3/utilities/logger'
105
119
 
106
120
  # Creates a marshal file by serializing the given obj
@@ -280,65 +294,6 @@ module OpenC3
280
294
  return log_file
281
295
  end
282
296
 
283
- # Writes a log file with information about the current configuration
284
- # including the Ruby version, OpenC3 version, whether you are on Windows, the
285
- # OpenC3 path, and the Ruby path along with the exception that
286
- # is passed in.
287
- #
288
- # @param [String] filename String to append to the exception log filename.
289
- # The filename will start with a date/time stamp.
290
- # @param [String] log_dir By default this method will write to the OpenC3
291
- # default log directory. By setting this parameter you can override the
292
- # directory the log will be written to.
293
- # @return [String|nil] The fully pathed log filename or nil if there was
294
- # an error creating the log file.
295
- def self.write_exception_file(exception, filename = 'exception', log_dir = nil)
296
- log_file = create_log_file(filename, log_dir) do |file|
297
- file.puts "Exception:"
298
- if exception
299
- file.puts exception.formatted
300
- file.puts
301
- else
302
- file.puts "No Exception Given"
303
- file.puts caller.join("\n")
304
- file.puts
305
- end
306
- file.puts "Caller Backtrace:"
307
- file.puts caller().join("\n")
308
- file.puts
309
-
310
- file.puts "Ruby Version: ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]"
311
- file.puts "Rubygems Version: #{Gem::VERSION}"
312
- file.puts "OpenC3 Version: #{OpenC3::VERSION}"
313
- file.puts "OpenC3::PATH: #{OpenC3::PATH}"
314
- file.puts ""
315
- file.puts "Environment:"
316
- file.puts "RUBYOPT: #{ENV['RUBYOPT']}"
317
- file.puts "RUBYLIB: #{ENV['RUBYLIB']}"
318
- file.puts "GEM_PATH: #{ENV['GEM_PATH']}"
319
- file.puts "GEMRC: #{ENV['GEMRC']}"
320
- file.puts "RI_DEVKIT: #{ENV['RI_DEVKIT']}"
321
- file.puts "GEM_HOME: #{ENV['GEM_HOME']}"
322
- file.puts "PYTHONUSERBASE: #{ENV['PYTHONUSERBASE']}"
323
- file.puts "PATH: #{ENV['PATH']}"
324
- file.puts ""
325
- file.puts "Ruby Path:\n #{$:.join("\n ")}\n\n"
326
- file.puts "Gems:"
327
- Gem.loaded_specs.values.map { |x| file.puts "#{x.name} #{x.version} #{x.platform}" }
328
- file.puts ""
329
- file.puts "All Threads Backtraces:"
330
- Thread.list.each do |thread|
331
- file.puts thread.backtrace.join("\n")
332
- file.puts
333
- end
334
- file.puts ""
335
- file.puts ""
336
- ensure
337
- file.close
338
- end
339
- return log_file
340
- end
341
-
342
297
  # Writes a log file with information about unexpected output
343
298
  #
344
299
  # @param [String] text The unexpected output text
@@ -367,7 +322,6 @@ module OpenC3
367
322
  def self.handle_fatal_exception(error, _try_gui = true)
368
323
  unless SystemExit === error or SignalException === error
369
324
  $openc3_fatal_exception = error
370
- self.write_exception_file(error)
371
325
  Logger.fatal "Fatal Exception! Exiting..."
372
326
  Logger.fatal error.formatted
373
327
  if $stdout != STDOUT
@@ -392,7 +346,6 @@ module OpenC3
392
346
  # @param try_gui [Boolean] Whether to try and create a GUI exception popup
393
347
  def self.handle_critical_exception(error, _try_gui = true)
394
348
  Logger.error "Critical Exception! #{error.formatted}"
395
- self.write_exception_file(error)
396
349
  end
397
350
 
398
351
  # Creates a Ruby Thread to run the given block. Rescues any exceptions and
@@ -412,7 +365,6 @@ module OpenC3
412
365
  Logger.error e.formatted
413
366
  retry_count += 1
414
367
  if retry_count <= retry_attempts
415
- self.write_exception_file(e)
416
368
  retry
417
369
  end
418
370
  handle_fatal_exception(e)
@@ -53,7 +53,7 @@ module OpenC3
53
53
  limits['red_high'] = event[:red_high]
54
54
  limits['green_low'] = event[:green_low] if event[:green_low] && event[:green_high]
55
55
  limits['green_high'] = event[:green_high] if event[:green_low] && event[:green_high]
56
- limits_settings[event[:limits_set]] = limits
56
+ limits_settings[event[:limits_set].to_s] = limits
57
57
  limits_settings['persistence_setting'] = event[:persistence] if event[:persistence]
58
58
  limits_settings['enabled'] = event[:enabled] if not event[:enabled].nil?
59
59
  Store.hset("#{scope}__current_limits_settings", field, JSON.generate(limits_settings, allow_nan: true))
@@ -117,7 +117,9 @@ module OpenC3
117
117
  # Allow caching for files that have a filename versioning strategy
118
118
  has_version_number = /(-|_|\.)\d+(-|_|\.)\d+(-|_|\.)\d+\./.match(filename)
119
119
  has_content_hash = /[\.-][a-f0-9]{20}\./.match(filename)
120
- return nil if has_version_number or has_content_hash
120
+ # Font files are immutable assets deployed with plugins and safe to cache
121
+ is_font = /\.(woff2?|eot|ttf|otf)$/.match(filename)
122
+ return nil if has_version_number or has_content_hash or is_font
121
123
  return 'no-store'
122
124
  end
123
125
 
@@ -197,6 +197,8 @@ module OpenC3
197
197
  target_lib_filename = "#{target_name.downcase}.#{@@language}"
198
198
  target_class = target_lib_filename.filename_to_class_name
199
199
  target_object = target_name.downcase
200
+ target_class.inspect # Remove unused variable warning. These are used in binding for generator
201
+ target_object.inspect # Remove unused variable warning. These are used in binding for generator
200
202
 
201
203
  process_template("#{TEMPLATES_DIR}/target", binding) do |filename|
202
204
  # Rename the template TARGET to our actual target named after the plugin
@@ -292,6 +294,7 @@ RUBY
292
294
  end
293
295
  microservice_filename = "#{microservice_name.downcase}.#{@@language}"
294
296
  microservice_class = microservice_filename.filename_to_class_name
297
+ microservice_class.inspect # Remove unused variable warning. These are used in binding for generator
295
298
 
296
299
  process_template("#{TEMPLATES_DIR}/microservice", binding) do |filename|
297
300
  # Rename the template MICROSERVICE to our actual microservice name
@@ -547,6 +550,7 @@ RUBY
547
550
  conversion_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_CONVERSION"
548
551
  conversion_basename = "#{conversion_name.downcase}.#{@@language}"
549
552
  conversion_class = conversion_basename.filename_to_class_name
553
+ conversion_class.inspect # Remove unused variable warning. These are used in binding for generator
550
554
  conversion_filename = "targets/#{target_name}/lib/#{conversion_basename}"
551
555
  if File.exist?(conversion_filename)
552
556
  abort("Conversion #{conversion_filename} already exists!")
@@ -601,6 +605,7 @@ RUBY
601
605
  processor_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_PROCESSOR"
602
606
  processor_basename = "#{processor_name.downcase}.#{@@language}"
603
607
  processor_class = processor_basename.filename_to_class_name
608
+ processor_class.inspect # Remove unused variable warning. These are used in binding for generator
604
609
  processor_filename = "targets/#{target_name}/lib/#{processor_basename}"
605
610
  if File.exist?(processor_filename)
606
611
  abort("Processor #{processor_filename} already exists!")
@@ -656,6 +661,7 @@ RUBY
656
661
  response_basename = "#{response_name.downcase}.#{@@language}"
657
662
  response_filename = "targets/#{target_name}/lib/#{response_basename}"
658
663
  response_class = response_basename.filename_to_class_name
664
+ response_class.inspect # Remove unused variable warning. These are used in binding for generator
659
665
  if File.exist?(response_filename)
660
666
  abort("response #{response_filename} already exists!")
661
667
  end
@@ -709,6 +715,7 @@ RUBY
709
715
  validator_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_COMMAND_VALIDATOR"
710
716
  validator_basename = "#{validator_name.downcase}.#{@@language}"
711
717
  validator_class = validator_basename.filename_to_class_name
718
+ validator_class.inspect # Remove unused variable warning. These are used in binding for generator
712
719
  validator_filename = "targets/#{target_name}/lib/#{validator_basename}"
713
720
  if File.exist?(validator_filename)
714
721
  abort("Command validator #{validator_filename} already exists!")
@@ -11,7 +11,7 @@
11
11
  # This file may also be used under the terms of a commercial license
12
12
  # if purchased from OpenC3, Inc.
13
13
 
14
- require 'openc3/packets/packet'
14
+ # require 'openc3/packets/packet' # Circular require
15
15
 
16
16
  module OpenC3
17
17
  module CmdLog
@@ -450,6 +450,7 @@ module OpenC3
450
450
  end
451
451
 
452
452
  def self.save_tool_config(scope, tool, name, data)
453
+ return unless ENV['OPENC3_LOCAL_MODE'] and Dir.exist?(OPENC3_LOCAL_MODE_PATH)
453
454
  json = JSON.parse(data, allow_nan: true, create_additions: true)
454
455
  config_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/tool_config/#{tool}/#{name}.json"
455
456
  return unless File.expand_path(config_path).start_with?(OPENC3_LOCAL_MODE_PATH)
@@ -460,6 +461,7 @@ module OpenC3
460
461
  end
461
462
 
462
463
  def self.delete_tool_config(scope, tool, name)
464
+ return unless ENV['OPENC3_LOCAL_MODE'] and Dir.exist?(OPENC3_LOCAL_MODE_PATH)
463
465
  config_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/tool_config/#{tool}/#{name}.json"
464
466
  return unless File.expand_path(config_path).start_with?(OPENC3_LOCAL_MODE_PATH)
465
467
  FileUtils.rm_f(config_path)
@@ -479,6 +481,7 @@ module OpenC3
479
481
  end
480
482
 
481
483
  def self.save_setting(scope, name, data)
484
+ return unless ENV['OPENC3_LOCAL_MODE'] and Dir.exist?(OPENC3_LOCAL_MODE_PATH)
482
485
  config_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/settings/#{name}.json"
483
486
  return unless File.expand_path(config_path).start_with?(OPENC3_LOCAL_MODE_PATH)
484
487
  FileUtils.mkdir_p(File.dirname(config_path))
@@ -17,7 +17,7 @@
17
17
 
18
18
  require 'openc3/operators/operator'
19
19
  require 'openc3/models/process_status_model'
20
- require 'openc3/models/scope_model'
20
+ # require 'openc3/models/scope_model' # Circular require
21
21
  require 'openc3/utilities/logger'
22
22
  require 'socket'
23
23
 
@@ -11,13 +11,13 @@
11
11
  # This file may also be used under the terms of a commercial license
12
12
  # if purchased from OpenC3, Inc.
13
13
 
14
- # TODO: Delegate to actual Python to verify that classes exist
15
- # and to get proper data from them like converted_type
16
-
17
14
  module OpenC3
18
15
  class PythonProxy
19
16
  attr_accessor :name
20
17
  attr_accessor :args
18
+ attr_accessor :converted_type
19
+ attr_accessor :converted_bit_size
20
+ attr_accessor :converted_array_size
21
21
 
22
22
  def initialize(type, class_name, *params)
23
23
  @type = type
@@ -25,6 +25,9 @@ module OpenC3
25
25
  @params = params
26
26
  @args = params
27
27
  @name = nil
28
+ @converted_type = nil
29
+ @converted_bit_size = nil
30
+ @converted_array_size = nil
28
31
  end
29
32
 
30
33
  def class
@@ -36,7 +39,11 @@ module OpenC3
36
39
  when "Processor"
37
40
  return { 'name' => @name, 'class' => @class_name, 'params' => @params }
38
41
  when "Conversion"
39
- return { 'class' => @class_name, 'params' => @params }
42
+ result = { 'class' => @class_name, 'params' => @params }
43
+ result['converted_type'] = @converted_type.to_s if @converted_type
44
+ result['converted_bit_size'] = @converted_bit_size if @converted_bit_size
45
+ result['converted_array_size'] = @converted_array_size if @converted_array_size
46
+ return result
40
47
  when "LimitsResponse"
41
48
  return { "class" => @class_name, 'params' => @params }
42
49
  else