openc3 5.2.0 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of openc3 might be problematic. Click here for more details.

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +108 -105
  3. data/data/config/interface_modifiers.yaml +22 -4
  4. data/data/config/item_modifiers.yaml +4 -2
  5. data/data/config/microservice.yaml +18 -0
  6. data/data/config/table_manager.yaml +2 -2
  7. data/data/config/tool.yaml +1 -1
  8. data/ext/openc3/ext/config_parser/config_parser.c +17 -2
  9. data/lib/openc3/api/api.rb +1 -0
  10. data/lib/openc3/api/interface_api.rb +12 -0
  11. data/lib/openc3/api/metrics_api.rb +97 -0
  12. data/lib/openc3/api/router_api.rb +14 -2
  13. data/lib/openc3/api/target_api.rb +24 -3
  14. data/lib/openc3/api/tlm_api.rb +5 -4
  15. data/lib/openc3/config/config_parser.rb +29 -4
  16. data/lib/openc3/core_ext/time.rb +6 -1
  17. data/lib/openc3/interfaces/interface.rb +27 -26
  18. data/lib/openc3/interfaces/mqtt_interface.rb +240 -0
  19. data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -61
  20. data/lib/openc3/interfaces/protocols/protocol.rb +6 -1
  21. data/lib/openc3/interfaces/simulated_target_interface.rb +1 -3
  22. data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -11
  23. data/lib/openc3/interfaces.rb +2 -3
  24. data/lib/openc3/logs/buffered_packet_log_reader.rb +2 -2
  25. data/lib/openc3/microservices/cleanup_microservice.rb +17 -1
  26. data/lib/openc3/microservices/decom_microservice.rb +12 -9
  27. data/lib/openc3/microservices/interface_microservice.rb +93 -9
  28. data/lib/openc3/microservices/log_microservice.rb +11 -5
  29. data/lib/openc3/microservices/microservice.rb +10 -9
  30. data/lib/openc3/microservices/periodic_microservice.rb +7 -0
  31. data/lib/openc3/microservices/reaction_microservice.rb +0 -33
  32. data/lib/openc3/microservices/reducer_microservice.rb +14 -10
  33. data/lib/openc3/microservices/text_log_microservice.rb +12 -3
  34. data/lib/openc3/microservices/timeline_microservice.rb +0 -6
  35. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -20
  36. data/lib/openc3/models/cvt_model.rb +103 -47
  37. data/lib/openc3/models/interface_model.rb +23 -0
  38. data/lib/openc3/models/metric_model.rb +53 -6
  39. data/lib/openc3/models/microservice_model.rb +15 -1
  40. data/lib/openc3/models/model.rb +1 -1
  41. data/lib/openc3/models/plugin_model.rb +6 -1
  42. data/lib/openc3/models/secret_model.rb +53 -0
  43. data/lib/openc3/models/target_model.rb +2 -2
  44. data/lib/openc3/models/tool_model.rb +17 -8
  45. data/lib/openc3/operators/microservice_operator.rb +25 -0
  46. data/lib/openc3/operators/operator.rb +5 -1
  47. data/lib/openc3/packets/packet.rb +21 -7
  48. data/lib/openc3/packets/packet_item.rb +3 -2
  49. data/lib/openc3/script/api_shared.rb +18 -2
  50. data/lib/openc3/script/script.rb +8 -0
  51. data/lib/openc3/script/script_runner.rb +1 -2
  52. data/lib/openc3/script/storage.rb +2 -1
  53. data/lib/openc3/script/suite.rb +15 -11
  54. data/lib/openc3/system/system.rb +6 -3
  55. data/lib/openc3/topics/interface_topic.rb +17 -1
  56. data/lib/openc3/topics/router_topic.rb +17 -1
  57. data/lib/openc3/utilities/aws_bucket.rb +20 -3
  58. data/lib/openc3/utilities/bucket.rb +1 -1
  59. data/lib/openc3/utilities/bucket_file_cache.rb +1 -1
  60. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  61. data/lib/openc3/utilities/local_mode.rb +1 -0
  62. data/lib/openc3/utilities/metric.rb +77 -101
  63. data/lib/openc3/utilities/redis_secrets.rb +46 -0
  64. data/lib/openc3/utilities/s3_autoload.rb +19 -9
  65. data/lib/openc3/utilities/secrets.rb +63 -0
  66. data/lib/openc3/utilities/target_file.rb +3 -1
  67. data/lib/openc3/version.rb +5 -5
  68. data/templates/plugin-template/LICENSE.txt +7 -0
  69. data/templates/plugin-template/README.md +4 -3
  70. data/templates/plugin-template/plugin.gemspec +4 -4
  71. metadata +22 -3
  72. data/data/config/_interfaces.yaml.err +0 -1017
@@ -0,0 +1,53 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU Affero General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ # This file may also be used under the terms of a commercial license
17
+ # if purchased from OpenC3, Inc.
18
+
19
+ require 'openc3/models/model'
20
+
21
+ module OpenC3
22
+ class SecretModel < Model
23
+ PRIMARY_KEY = 'openc3__secrets'
24
+
25
+ # NOTE: The following three class methods are used by the ModelController
26
+ # and are reimplemented to enable various Model class methods to work
27
+ def self.get(name:, scope:)
28
+ super("#{scope}__#{PRIMARY_KEY}", name: name)
29
+ end
30
+
31
+ def self.names(scope:)
32
+ super("#{scope}__#{PRIMARY_KEY}")
33
+ end
34
+
35
+ def self.all(scope:)
36
+ super("#{scope}__#{PRIMARY_KEY}")
37
+ end
38
+ # END NOTE
39
+
40
+ def initialize(name:, value:, scope:)
41
+ super("#{scope}__#{PRIMARY_KEY}", name: name, scope: scope)
42
+ @value = value
43
+ end
44
+
45
+ # @return [Hash] JSON encoding of this model
46
+ def as_json(*a)
47
+ {
48
+ 'name' => @name,
49
+ 'value' => @value.as_json(*a),
50
+ }
51
+ end
52
+ end
53
+ end
@@ -101,7 +101,7 @@ module OpenC3
101
101
  targets[target_name]['modified'] = true if targets[target_name]
102
102
  end
103
103
  else
104
- modified_targets = Bucket.getClient().list_directories(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: "DEFAULT/targets_modified/")
104
+ modified_targets = Bucket.getClient().list_files(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: "DEFAULT/targets_modified/", only_directories: true)
105
105
  modified_targets.each do |target_name|
106
106
  # A target could have been deleted without removing the modified files
107
107
  # Thus we have to check for the existance of the target_name key
@@ -313,7 +313,7 @@ module OpenC3
313
313
  reduced_day_log_retain_time: nil,
314
314
  cleanup_poll_time: 900,
315
315
  needs_dependencies: false,
316
- target_microservices: {},
316
+ target_microservices: {'REDUCER' => [[]]},
317
317
  scope:
318
318
  )
319
319
  super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at,
@@ -93,8 +93,15 @@ module OpenC3
93
93
  # The ToolsTab.vue calls the ToolsController which uses this method to reorder the tools
94
94
  # Position is index in the list starting with 0 = first
95
95
  def self.set_position(name:, position:, scope:)
96
- position = Integer(position)
97
- next_position = position + 1
96
+ moving = from_json(get(name: name, scope: scope), scope: scope)
97
+ old_pos = moving.position
98
+ new_pos = Integer(position)
99
+ direction = :down
100
+ if (old_pos == new_pos)
101
+ return # we're not doing anything
102
+ elsif (new_pos > old_pos)
103
+ direction = :up
104
+ end
98
105
 
99
106
  # Go through all the tools and reorder
100
107
  all(scope: scope).each do |_tool_name, tool|
@@ -102,12 +109,14 @@ module OpenC3
102
109
  # Update the requested model to the new position
103
110
  if tool_model.name == name
104
111
  tool_model.position = position
105
- # Move existing tools down in the order
106
- elsif position > 0 && position >= tool_model.position
107
- tool_model.position -= 1
108
- else # Move existing tools up in the order
109
- tool_model.position = next_position
110
- next_position += 1
112
+ elsif direction == :down
113
+ if tool_model.position >= new_pos && tool_model.position < old_pos
114
+ tool_model.position += 1
115
+ end
116
+ else # up
117
+ if tool_model.position > old_pos && tool_model.position <= new_pos
118
+ tool_model.position -= 1
119
+ end
111
120
  end
112
121
  tool_model.update
113
122
  end
@@ -23,8 +23,10 @@
23
23
  require 'openc3'
24
24
  require 'openc3/models/microservice_model'
25
25
  require 'openc3/operators/operator'
26
+ require 'openc3/utilities/secrets'
26
27
  require 'redis'
27
28
  require 'open3'
29
+ require 'fileutils'
28
30
 
29
31
  module OpenC3
30
32
  # Creates new OperatorProcess objects based on querying the Redis key value store.
@@ -34,6 +36,7 @@ module OpenC3
34
36
  Logger.microservice_name = "MicroserviceOperator"
35
37
  super
36
38
 
39
+ @secrets = Secrets.getClient
37
40
  @microservices = {}
38
41
  @previous_microservices = {}
39
42
  @new_microservices = {}
@@ -53,6 +56,28 @@ module OpenC3
53
56
  env['OPENC3_MICROSERVICE_NAME'] = microservice_name
54
57
  container = microservice_config["container"]
55
58
  scope = microservice_name.split("__")[0]
59
+
60
+ # Setup secrets for microservice
61
+ secrets = microservice_config["secrets"]
62
+ if secrets
63
+ secrets.each do |type, secret_name, env_name_or_path|
64
+ secret_value = @secrets.get(secret_name, scope: scope)
65
+ if secret_value
66
+ case type
67
+ when 'ENV'
68
+ env[env_name_or_path] = secret_value
69
+ when 'FILE'
70
+ FileUtils.mkdir_p(File.dirname(env_name_or_path))
71
+ File.open(env_name_or_path, 'wb') do |file|
72
+ file.write(secret_value)
73
+ end
74
+ end
75
+ else
76
+ Logger.error("Microservice #{microservice_name} references unknown secret: #{secret_name}")
77
+ end
78
+ end
79
+ end
80
+
56
81
  return process_definition, work_dir, env, scope, container
57
82
  end
58
83
 
@@ -160,7 +160,11 @@ module OpenC3
160
160
  def soft_stop
161
161
  Thread.new do
162
162
  Logger.info("Soft shutting down process: #{cmd_line()}", scope: @scope)
163
- Process.kill("SIGINT", @process.pid) if @process # Signal the process to stop
163
+ begin
164
+ Process.kill("SIGINT", @process.pid) if @process # Signal the process to stop
165
+ rescue Errno::ESRCH
166
+ # Process already gone
167
+ end
164
168
  end
165
169
  end
166
170
 
@@ -33,7 +33,7 @@ module OpenC3
33
33
  # as managing PacketItem's limit states.
34
34
  class Packet < Structure
35
35
  RESERVED_ITEM_NAMES = ['PACKET_TIMESECONDS'.freeze, 'PACKET_TIMEFORMATTED'.freeze, 'RECEIVED_TIMESECONDS'.freeze, 'RECEIVED_TIMEFORMATTED'.freeze, 'RECEIVED_COUNT'.freeze]
36
- CATCH_ALL_STATE = 'ANY'
36
+ ANY_STATE = 'ANY'
37
37
 
38
38
  # @return [String] Name of the target this packet is associated with
39
39
  attr_reader :target_name
@@ -627,8 +627,8 @@ module OpenC3
627
627
  value = value.map do |val, index|
628
628
  if item.states.key(val)
629
629
  item.states.key(val)
630
- elsif item.states.values.include?(CATCH_ALL_STATE)
631
- item.states.key(CATCH_ALL_STATE)
630
+ elsif item.states.values.include?(ANY_STATE)
631
+ item.states.key(ANY_STATE)
632
632
  else
633
633
  apply_format_string_and_units(item, val, value_type)
634
634
  end
@@ -637,8 +637,8 @@ module OpenC3
637
637
  state_value = item.states.key(value)
638
638
  if state_value
639
639
  value = state_value
640
- elsif item.states.values.include?(CATCH_ALL_STATE)
641
- value = item.states.key(CATCH_ALL_STATE)
640
+ elsif item.states.values.include?(ANY_STATE)
641
+ value = item.states.key(ANY_STATE)
642
642
  else
643
643
  value = apply_format_string_and_units(item, value, value_type)
644
644
  end
@@ -653,7 +653,14 @@ module OpenC3
653
653
  end
654
654
  end
655
655
  else
656
- raise ArgumentError, "Unknown value type on read: #{value_type}"
656
+ # Trim a potentially long string (like if they accidentally pass buffer as value_type)
657
+ if value_type.to_s.length > 10
658
+ value_type = value_type.to_s[0...10]
659
+ # Ensure we're not trying to output binary
660
+ value_type = value_type.simple_formatted unless value_type.is_printable?
661
+ value_type += '...'
662
+ end
663
+ raise ArgumentError, "Unknown value type '#{value_type}', must be :RAW, :CONVERTED, :FORMATTED, or :WITH_UNITS"
657
664
  end
658
665
  return value
659
666
  end
@@ -716,7 +723,14 @@ module OpenC3
716
723
  when :FORMATTED, :WITH_UNITS
717
724
  raise ArgumentError, "Invalid value type on write: #{value_type}"
718
725
  else
719
- raise ArgumentError, "Unknown value type on write: #{value_type}"
726
+ # Trim potentially long string (like if they accidentally pass buffer as value_type)
727
+ if value_type.to_s.length > 10
728
+ value_type = value_type.to_s[0...10]
729
+ # Ensure we're not trying to output binary
730
+ value_type = value_type.simple_formatted unless value_type.is_printable?
731
+ value_type += '...'
732
+ end
733
+ raise ArgumentError, "Unknown value type '#{value_type}', must be :RAW, :CONVERTED, :FORMATTED, or :WITH_UNITS"
720
734
  end
721
735
  if @read_conversion_cache
722
736
  synchronize() do
@@ -493,7 +493,7 @@ module OpenC3
493
493
  if self.limits.values
494
494
  config['limits'] ||= {}
495
495
  config['limits']['persistence_setting'] = self.limits.persistence_setting
496
- config['limits']['enabled'] = true if self.limits.enabled
496
+ config['limits']['response'] = self.limits.response.to_s if self.limits.response
497
497
  self.limits.values.each do |limits_set, limits_values|
498
498
  limits = {}
499
499
  limits['red_low'] = limits_values[0]
@@ -557,9 +557,10 @@ module OpenC3
557
557
 
558
558
  item.limits = PacketItemLimits.new
559
559
  if hash['limits']
560
- # Delete these keys so the only ones left are limits sets
560
+ # Delete the keys so the only ones left are limits sets
561
561
  persistence_setting = hash['limits'].delete('persistence_setting')
562
562
  item.limits.persistence_setting = persistence_setting if persistence_setting
563
+ hash['limits'].delete('response') # Can't round trip response
563
564
  item.limits.enabled = true if hash['limits'].delete('enabled')
564
565
  values = {}
565
566
  hash['limits'].each do |set, items|
@@ -17,7 +17,7 @@
17
17
  # All changes Copyright 2022, 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
  require 'openc3/script/extract'
@@ -500,7 +500,20 @@ module OpenC3
500
500
  def load_utility(procedure_name)
501
501
  return start(procedure_name)
502
502
  end
503
- alias require_utility load_utility
503
+ def require_utility(procedure_name)
504
+ @require_utility_cache ||= {}
505
+ if @require_utility_cache[procedure_name]
506
+ return false
507
+ else
508
+ @require_utility_cache[procedure_name] = true
509
+ begin
510
+ return start(procedure_name)
511
+ rescue LoadError
512
+ @require_utility_cache[procedure_name] = false
513
+ raise # reraise the error
514
+ end
515
+ end
516
+ end
504
517
 
505
518
  ###########################################################################
506
519
  # Private implementation details
@@ -714,6 +727,7 @@ module OpenC3
714
727
 
715
728
  def _openc3_script_wait_implementation(target_name, packet_name, item_name, value_type, timeout, polling_rate, exp_to_eval, scope: $openc3_scope, token: $openc3_token, &block)
716
729
  end_time = Time.now.sys + timeout
730
+ raise "Invalid comparison to non-ascii value" unless exp_to_eval.is_printable?
717
731
 
718
732
  while true
719
733
  work_start = Time.now.sys
@@ -792,6 +806,7 @@ module OpenC3
792
806
  # Wait on an expression to be true.
793
807
  def openc3_script_wait_implementation_expression(exp_to_eval, timeout, polling_rate, context, scope: $openc3_scope, token: $openc3_token)
794
808
  end_time = Time.now.sys + timeout
809
+ raise "Invalid comparison to non-ascii value" unless exp_to_eval.is_printable?
795
810
 
796
811
  while true
797
812
  work_start = Time.now.sys
@@ -828,6 +843,7 @@ module OpenC3
828
843
  end
829
844
 
830
845
  def check_eval(target_name, packet_name, item_name, comparison_to_eval, value, scope: $openc3_scope, token: $openc3_token)
846
+ raise "Invalid comparison to non-ascii value" unless comparison_to_eval.is_printable?
831
847
  string = "value " + comparison_to_eval
832
848
  check_str = "CHECK: #{_upcase(target_name, packet_name, item_name)} #{comparison_to_eval}"
833
849
  # Show user the check against a quoted string
@@ -178,6 +178,14 @@ module OpenC3
178
178
  print "Details: #{details}\n" if details
179
179
  gets.chomp
180
180
  end
181
+
182
+ def step_mode
183
+ # NOOP
184
+ end
185
+
186
+ def run_mode
187
+ # NOOP
188
+ end
181
189
  end
182
190
 
183
191
  # Provides a proxy to the JsonDRbObject which communicates with the API server
@@ -13,7 +13,7 @@
13
13
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
14
  # GNU Affero General Public License for more details.
15
15
  #
16
- # This file may also be used under the terms of a commercial license
16
+ # This file may also be used under the terms of a commercial license
17
17
  # if purchased from OpenC3, Inc.
18
18
 
19
19
  module OpenC3
@@ -236,6 +236,5 @@ module OpenC3
236
236
  return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
237
237
  end
238
238
  end
239
-
240
239
  end
241
240
  end
@@ -157,7 +157,8 @@ module OpenC3
157
157
  case ENV['OPENC3_CLOUD']
158
158
  when 'local'
159
159
  uri = URI.parse("http://openc3-minio:9000" + url)
160
- # when 'aws'
160
+ when 'aws'
161
+ uri = URI.parse("https://s3.#{ENV['AWS_REGION']}.amazonaws.com" + url)
161
162
  when 'gcp'
162
163
  uri = URI.parse("https://storage.googleapis.com" + url)
163
164
  # when 'azure'
@@ -17,7 +17,7 @@
17
17
  # All changes Copyright 2022, 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
  require 'openc3/script/exceptions'
@@ -37,14 +37,12 @@ module OpenC3
37
37
  # START PUBLIC API
38
38
  ###########################################################################
39
39
 
40
- # Create a new Suite
41
- def initialize
42
- @scripts = {}
43
- @plans = []
44
- end
40
+ # Explictly avoid creating an initialize method which forces end users to call super()
45
41
 
46
42
  # Add a group to the suite
47
43
  def add_group(group_class)
44
+ @scripts ||= {}
45
+ @plans ||= []
48
46
  group_class = Object.const_get(group_class.to_s.intern) unless group_class.class == Class
49
47
  @scripts[group_class] = group_class.new unless @scripts[group_class]
50
48
  @plans << [:GROUP, group_class, nil]
@@ -52,6 +50,8 @@ module OpenC3
52
50
 
53
51
  # Add a script to the suite
54
52
  def add_script(group_class, script)
53
+ @scripts ||= {}
54
+ @plans ||= []
55
55
  group_class = Object.const_get(group_class.to_s.intern) unless group_class.class == Class
56
56
  @scripts[group_class] = group_class.new unless @scripts[group_class]
57
57
  @plans << [:SCRIPT, group_class, script]
@@ -59,6 +59,8 @@ module OpenC3
59
59
 
60
60
  # Add a group setup to the suite
61
61
  def add_group_setup(group_class)
62
+ @scripts ||= {}
63
+ @plans ||= []
62
64
  group_class = Object.const_get(group_class.to_s.intern) unless group_class.class == Class
63
65
  @scripts[group_class] = group_class.new unless @scripts[group_class]
64
66
  @plans << [:GROUP_SETUP, group_class, nil]
@@ -66,6 +68,8 @@ module OpenC3
66
68
 
67
69
  # Add a group teardown to the suite
68
70
  def add_group_teardown(group_class)
71
+ @scripts ||= {}
72
+ @plans ||= []
69
73
  group_class = Object.const_get(group_class.to_s.intern) unless group_class.class == Class
70
74
  @scripts[group_class] = group_class.new unless @scripts[group_class]
71
75
  @plans << [:GROUP_TEARDOWN, group_class, nil]
@@ -269,11 +273,7 @@ module OpenC3
269
273
  @@abort_on_exception = false
270
274
  @@current_result = nil
271
275
 
272
- def initialize
273
- @output_io = StringIO.new('', 'r+')
274
- $stdout = Stdout.instance
275
- $stderr = Stderr.instance
276
- end
276
+ # Explictly avoid creating an initialize method which forces end users to call super()
277
277
 
278
278
  def self.abort_on_exception
279
279
  @@abort_on_exception
@@ -350,7 +350,11 @@ module OpenC3
350
350
 
351
351
  # Verify script method exists
352
352
  if object.class.method_defined?(method_name)
353
+ @output_io ||= StringIO.new('', 'r+')
353
354
  # Capture STDOUT and STDERR
355
+ # $stdout & $stderr must be set to change output
356
+ $stdout = Stdout.instance
357
+ $stderr = Stderr.instance
354
358
  $stdout.add_stream(@output_io)
355
359
  $stderr.add_stream(@output_io)
356
360
 
@@ -131,11 +131,14 @@ module OpenC3
131
131
 
132
132
  target = Target.new(target_name, target_config_dir)
133
133
  @targets[target.name] = target
134
+ errors = [] # Store all errors processing the cmd_tlm files
134
135
  target.cmd_tlm_files.each do |cmd_tlm_file|
135
136
  @packet_config.process_file(cmd_tlm_file, target.name)
136
- rescue Exception => err
137
- Logger.error "Problem processing #{cmd_tlm_file}: #{err}."
138
- raise err
137
+ rescue Exception => error
138
+ errors << "Error processing #{cmd_tlm_file}:\n#{error.message}"
139
+ end
140
+ unless errors.empty?
141
+ raise errors.join("\n")
139
142
  end
140
143
  end
141
144
  end
@@ -38,7 +38,7 @@ module OpenC3
38
38
  def self.receive_commands(interface, scope:)
39
39
  while true
40
40
  Topic.read_topics(InterfaceTopic.topics(interface, scope: scope)) do |topic, msg_id, msg_hash, redis|
41
- result = yield topic, msg_hash
41
+ result = yield topic, msg_id, msg_hash, redis
42
42
  ack_topic = topic.split("__")
43
43
  ack_topic[1] = 'ACK' + ack_topic[1]
44
44
  ack_topic = ack_topic.join("__")
@@ -76,5 +76,21 @@ module OpenC3
76
76
  sleep 1 # Give some time for the interface to shutdown
77
77
  InterfaceTopic.clear_topics(InterfaceTopic.topics(interface, scope: scope))
78
78
  end
79
+
80
+ def interface_cmd(interface_name, cmd_name, *cmd_params, scope:)
81
+ data = {}
82
+ data['cmd_name'] = cmd_name
83
+ data['cmd_params'] = cmd_params
84
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'interface_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100)
85
+ end
86
+
87
+ def protocol_cmd(interface_name, cmd_name, *cmd_params, read_write: :READ_WRITE, index: -1, scope:)
88
+ data = {}
89
+ data['cmd_name'] = cmd_name
90
+ data['cmd_params'] = cmd_params
91
+ data['read_write'] = read_write.to_s.upcase
92
+ data['index'] = index
93
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'protocol_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100)
94
+ end
79
95
  end
80
96
  end
@@ -40,7 +40,7 @@ module OpenC3
40
40
  def self.receive_telemetry(router, scope:)
41
41
  while true
42
42
  Topic.read_topics(RouterTopic.topics(router, scope: scope)) do |topic, msg_id, msg_hash, redis|
43
- result = yield topic, msg_hash
43
+ result = yield topic, msg_id, msg_hash, redis
44
44
  if /CMD}ROUTER/.match?(topic)
45
45
  ack_topic = topic.split("__")
46
46
  ack_topic[1] = 'ACK' + ack_topic[1]
@@ -88,5 +88,21 @@ module OpenC3
88
88
  sleep 1 # Give some time for the interface to shutdown
89
89
  RouterTopic.clear_topics(RouterTopic.topics(router, scope: scope))
90
90
  end
91
+
92
+ def router_cmd(router_name, cmd_name, *cmd_params, scope:)
93
+ data = {}
94
+ data['cmd_name'] = cmd_name
95
+ data['cmd_params'] = cmd_params
96
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'router_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100)
97
+ end
98
+
99
+ def protocol_cmd(router_name, cmd_name, *cmd_params, read_write: :READ_WRITE, index: -1, scope:)
100
+ data = {}
101
+ data['cmd_name'] = cmd_name
102
+ data['cmd_params'] = cmd_params
103
+ data['read_write'] = read_write.to_s.upcase
104
+ data['index'] = index
105
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'protocol_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100)
106
+ end
91
107
  end
92
108
  end
@@ -121,15 +121,20 @@ module OpenC3
121
121
  result
122
122
  end
123
123
 
124
- # Lists the directories under a specified path
125
- def list_directories(bucket:, path:)
124
+ # Lists the files under a specified path
125
+ def list_files(bucket:, path:, only_directories: false)
126
126
  # Trailing slash is important in AWS S3 when listing files
127
127
  # See https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Types/ListObjectsV2Output.html#common_prefixes-instance_method
128
128
  if path[-1] != '/'
129
129
  path += '/'
130
130
  end
131
+ # If we're searching for the root then kill the path or AWS will return nothing
132
+ path = nil if path == '/'
133
+
131
134
  token = nil
132
135
  result = []
136
+ dirs = []
137
+ files = []
133
138
  while true
134
139
  resp = @client.list_objects_v2({
135
140
  bucket: bucket,
@@ -141,7 +146,19 @@ module OpenC3
141
146
  resp.common_prefixes.each do |item|
142
147
  # If path was DEFAULT/targets_modified/ then the
143
148
  # results look like DEFAULT/targets_modified/INST/
144
- result << item.prefix.split('/')[-1]
149
+ dirs << item.prefix.split('/')[-1]
150
+ end
151
+ if only_directories
152
+ result = dirs
153
+ else
154
+ resp.contents.each do |aws_item|
155
+ item = {}
156
+ item['name'] = aws_item.key.split('/')[-1]
157
+ item['modified'] = aws_item.last_modified
158
+ item['size'] = aws_item.size
159
+ files << item
160
+ end
161
+ result = [dirs, files]
145
162
  end
146
163
  break unless resp.is_truncated
147
164
  token = resp.next_continuation_token
@@ -57,7 +57,7 @@ module OpenC3
57
57
  raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
58
58
  end
59
59
 
60
- def list_directories(bucket:, path:)
60
+ def list_files(bucket:, path:, only_directories: false)
61
61
  raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
62
62
  end
63
63
 
@@ -73,7 +73,7 @@ class BucketFile
73
73
  local_path = "#{BucketFileCache.instance.cache_dir}/#{File.basename(@bucket_path)}"
74
74
  unless File.exist?(local_path)
75
75
  OpenC3::Logger.debug "Retrieving #{@bucket_path} from logs bucket"
76
- client.get_object(bucket: "logs", key: @bucket_path, path: local_path)
76
+ client.get_object(bucket: ENV['OPENC3_LOGS_BUCKET'], key: @bucket_path, path: local_path)
77
77
  if File.exist?(local_path)
78
78
  basename = File.basename(local_path)
79
79
  if uncompress and File.extname(basename) == ".gz"
@@ -46,7 +46,7 @@ module OpenC3
46
46
  return oldest_list
47
47
  end
48
48
 
49
- directories = client.list_directories(bucket: bucket, path: prefix)
49
+ directories = client.list_files(bucket: bucket, path: prefix, only_directories: true)
50
50
  filtered_directories = filter_directories_to_time_range(directories, start_time, end_time)
51
51
  filtered_directories.each do |directory|
52
52
  directory_files = client.list_objects(bucket: bucket, prefix: "#{prefix}/#{directory}", max_request: max_request, max_total: max_total)
@@ -30,6 +30,7 @@ module OpenC3
30
30
  DEFAULT_PLUGINS = [
31
31
  'openc3-cosmos-tool-admin',
32
32
  'openc3-cosmos-tool-autonomic',
33
+ 'openc3-cosmos-tool-bucketexplorer',
33
34
  'openc3-cosmos-tool-calendar',
34
35
  'openc3-cosmos-tool-cmdsender',
35
36
  'openc3-cosmos-tool-cmdtlmserver',