openc3 7.0.0 → 7.1.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +105 -13
  3. data/bin/pipinstall +38 -6
  4. data/data/config/command_modifiers.yaml +1 -0
  5. data/data/config/item_modifiers.yaml +2 -1
  6. data/data/config/microservice.yaml +12 -1
  7. data/data/config/parameter_modifiers.yaml +49 -7
  8. data/data/config/table_parameter_modifiers.yaml +3 -1
  9. data/data/config/target.yaml +11 -0
  10. data/data/config/target_config.yaml +6 -2
  11. data/lib/openc3/accessors/template_accessor.rb +9 -0
  12. data/lib/openc3/api/cmd_api.rb +2 -1
  13. data/lib/openc3/api/metrics_api.rb +11 -1
  14. data/lib/openc3/api/tlm_api.rb +21 -6
  15. data/lib/openc3/core_ext/faraday.rb +1 -1
  16. data/lib/openc3/interfaces/interface.rb +1 -6
  17. data/lib/openc3/io/json_api.rb +1 -1
  18. data/lib/openc3/logs/log_writer.rb +3 -1
  19. data/lib/openc3/microservices/decom_common.rb +128 -0
  20. data/lib/openc3/microservices/decom_microservice.rb +27 -96
  21. data/lib/openc3/microservices/interface_decom_common.rb +28 -10
  22. data/lib/openc3/microservices/interface_microservice.rb +16 -9
  23. data/lib/openc3/microservices/log_microservice.rb +1 -1
  24. data/lib/openc3/microservices/microservice.rb +3 -2
  25. data/lib/openc3/microservices/queue_microservice.rb +1 -1
  26. data/lib/openc3/microservices/scope_cleanup_microservice.rb +60 -46
  27. data/lib/openc3/microservices/text_log_microservice.rb +1 -2
  28. data/lib/openc3/models/cvt_model.rb +24 -13
  29. data/lib/openc3/models/db_sharded_model.rb +110 -0
  30. data/lib/openc3/models/interface_model.rb +9 -0
  31. data/lib/openc3/models/interface_status_model.rb +33 -3
  32. data/lib/openc3/models/metric_model.rb +96 -37
  33. data/lib/openc3/models/microservice_model.rb +7 -0
  34. data/lib/openc3/models/microservice_status_model.rb +30 -3
  35. data/lib/openc3/models/plugin_model.rb +9 -1
  36. data/lib/openc3/models/python_package_model.rb +1 -1
  37. data/lib/openc3/models/reaction_model.rb +27 -9
  38. data/lib/openc3/models/reingest_job_model.rb +153 -0
  39. data/lib/openc3/models/scope_model.rb +3 -2
  40. data/lib/openc3/models/script_status_model.rb +4 -20
  41. data/lib/openc3/models/target_model.rb +113 -100
  42. data/lib/openc3/models/trigger_model.rb +24 -7
  43. data/lib/openc3/packets/packet_config.rb +4 -1
  44. data/lib/openc3/script/api_shared.rb +39 -2
  45. data/lib/openc3/script/calendar.rb +32 -10
  46. data/lib/openc3/script/extract.rb +46 -13
  47. data/lib/openc3/script/script.rb +2 -2
  48. data/lib/openc3/script/script_runner.rb +4 -4
  49. data/lib/openc3/script/telemetry.rb +3 -3
  50. data/lib/openc3/script/web_socket_api.rb +29 -22
  51. data/lib/openc3/system/system.rb +20 -3
  52. data/lib/openc3/topics/command_decom_topic.rb +4 -2
  53. data/lib/openc3/topics/command_topic.rb +8 -5
  54. data/lib/openc3/topics/decom_interface_topic.rb +31 -11
  55. data/lib/openc3/topics/interface_topic.rb +88 -27
  56. data/lib/openc3/topics/limits_event_topic.rb +62 -41
  57. data/lib/openc3/topics/router_topic.rb +61 -21
  58. data/lib/openc3/topics/system_events_topic.rb +18 -1
  59. data/lib/openc3/topics/telemetry_decom_topic.rb +2 -1
  60. data/lib/openc3/topics/telemetry_topic.rb +4 -2
  61. data/lib/openc3/topics/topic.rb +77 -5
  62. data/lib/openc3/utilities/aws_bucket.rb +2 -0
  63. data/lib/openc3/utilities/cli_generator.rb +3 -2
  64. data/lib/openc3/utilities/ctrf.rb +231 -0
  65. data/lib/openc3/utilities/metric.rb +15 -1
  66. data/lib/openc3/utilities/questdb_client.rb +177 -40
  67. data/lib/openc3/utilities/reingest_job.rb +377 -0
  68. data/lib/openc3/utilities/ruby_lex_utils.rb +2 -0
  69. data/lib/openc3/utilities/store_autoload.rb +78 -52
  70. data/lib/openc3/utilities/store_queued.rb +20 -12
  71. data/lib/openc3/version.rb +5 -5
  72. data/templates/plugin/plugin.gemspec +13 -1
  73. data/templates/tool_angular/package.json +2 -2
  74. data/templates/tool_react/package.json +1 -1
  75. data/templates/tool_svelte/package.json +1 -1
  76. data/templates/tool_vue/package.json +3 -4
  77. data/templates/tool_vue/src/router.js +2 -2
  78. data/templates/widget/package.json +2 -2
  79. metadata +8 -3
@@ -288,6 +288,7 @@ module OpenC3
288
288
  start_time = Time.now.sys
289
289
  success, value = _openc3_script_wait_implementation_comparison(target_name, packet_name, item_name, type, comparison_to_eval, timeout, polling_rate, scope: scope, token: token, &block)
290
290
  value = "'#{value}'" if value.is_a? String # Show user the check against a quoted string
291
+ value = 'nil' if value.nil? # Show user nil value as 'nil'
291
292
  time_diff = Time.now.sys - start_time
292
293
  check_str = "CHECK: #{_upcase(target_name, packet_name, item_name)}"
293
294
  if comparison_to_eval
@@ -531,7 +532,7 @@ module OpenC3
531
532
  if comparison_to_eval
532
533
  _check_eval(target_name, packet_name, item_name, comparison_to_eval, value)
533
534
  else
534
- puts "CHECK: #{_upcase(target_name, packet_name, item_name)} == #{value}"
535
+ puts "CHECK: #{_upcase(target_name, packet_name, item_name)} == #{value.nil? ? 'nil' : value.inspect}"
535
536
  end
536
537
  end
537
538
 
@@ -632,6 +633,7 @@ module OpenC3
632
633
  start_time = Time.now.sys
633
634
  success, value = _openc3_script_wait_implementation_comparison(target_name, packet_name, item_name, value_type, comparison_to_eval, timeout, polling_rate, scope: scope, token: token)
634
635
  value = "'#{value}'" if value.is_a? String # Show user the check against a quoted string
636
+ value = 'nil' if value.nil? # Show user nil value as 'nil'
635
637
  time_diff = Time.now.sys - start_time
636
638
  wait_str = "WAIT: #{_upcase(target_name, packet_name, item_name)} #{comparison_to_eval}"
637
639
  value_str = "with value == #{value} after waiting #{time_diff} seconds"
@@ -863,8 +865,20 @@ module OpenC3
863
865
  # Show user the check against a quoted string
864
866
  # Note: We have to preserve the original 'value' variable because we're going to eval against it
865
867
  value_str = value.is_a?(String) ? "'#{value}'" : value
868
+ value_str = 'nil' if value.nil? # Show user nil value as 'nil'
866
869
  with_value = "with value == #{value_str}"
867
- if eval(string)
870
+
871
+ eval_is_valid = _check_eval_validity(value, comparison_to_eval)
872
+ unless eval_is_valid
873
+ message = "Invalid comparison for types"
874
+ if $disconnect
875
+ puts "ERROR: #{message}"
876
+ else
877
+ raise CheckError, message
878
+ end
879
+ end
880
+
881
+ if eval_is_valid && eval(string)
868
882
  puts "#{check_str} success #{with_value}"
869
883
  else
870
884
  message = "#{check_str} failed #{with_value}"
@@ -883,5 +897,28 @@ module OpenC3
883
897
  raise e
884
898
  end
885
899
  end
900
+
901
+ def _check_eval_validity(value, comparison)
902
+ return true if comparison.nil? || comparison.empty?
903
+
904
+ begin
905
+ operator, operand = extract_operator_and_operand_from_comparison(comparison)
906
+ rescue RuntimeError => e
907
+ if e.message.include?("Unable to parse operand")
908
+ # If we can't parse the operand, let the eval happen anyway
909
+ # It will raise an appropriate error (like NameError for undefined constants)
910
+ return true
911
+ end
912
+ raise # Re-raise invalid operator errors
913
+ rescue JSON::ParserError
914
+ return true
915
+ end
916
+
917
+ if [">=", "<=", ">", "<"].include?(operator)
918
+ return false if value.nil? || operand.nil? || value.is_a?(Array) || operand.is_a?(Array)
919
+ end
920
+
921
+ return true
922
+ end
886
923
  end
887
924
  end
@@ -61,20 +61,28 @@ module OpenC3
61
61
  # @param data [Hash, optional] Additional data to associate with the activity. Defaults to {}. Any activity can provide "username", "notes", and "customTitle". "command", "script", and "reserve" keys are reserves for the corresponding activity kind, with "environment" also available for script activities.
62
62
  # @param scope [String, optional] The scope of the activity. Defaults to OPENC3_SCOPE, must correspond to the timeline.
63
63
  def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
64
- kind = kind.to_s.downcase()
65
- kinds = %w(command script reserve)
66
- unless kinds.include?(kind)
67
- raise "Unknown kind: #{kind}. Must be one of #{kinds.join(', ')}."
68
- end
69
- post_data = {}
70
- post_data['start'] = start.to_datetime.iso8601
71
- post_data['stop'] = stop.to_datetime.iso8601
72
- post_data['kind'] = kind
73
- post_data['data'] = data
64
+ post_data = _build_activity_data(kind, start, stop, data)
74
65
  response = $api_server.request('post', "/openc3-api/timeline/#{name}/activities", data: post_data, json: true, scope: scope)
75
66
  return _cal_handle_response(response, 'Failed to create timeline activity')
76
67
  end
77
68
 
69
+ # Updates an existing activity on the specified timeline.
70
+ #
71
+ # @param name [String] The name of the timeline.
72
+ # @param id [Integer] The start time / score of the activity (Unix seconds).
73
+ # @param kind [String] The kind of activity. Must be one of "COMMAND", "SCRIPT", or "RESERVE".
74
+ # @param start [DateTime] The new start time of the activity.
75
+ # @param stop [DateTime] The new stop time of the activity.
76
+ # @param data [Hash, optional] Additional data to associate with the activity. Defaults to {}. Any activity can provide "username", "notes", and "customTitle". "command", "script", and "reserve" keys are reserved for the corresponding activity kind, with "environment" also available for script activities.
77
+ # @param uuid [String] The UUID of the activity.
78
+ # @param scope [String, optional] The scope of the activity. Defaults to OPENC3_SCOPE.
79
+ def update_timeline_activity(name, id:, kind:, start:, stop:, uuid:, data: {}, scope: $openc3_scope)
80
+ post_data = _build_activity_data(kind, start, stop, data)
81
+ url = "/openc3-api/timeline/#{name}/activity/#{id}/#{uuid}"
82
+ response = $api_server.request('put', url, data: post_data, json: true, scope: scope)
83
+ return _cal_handle_response(response, 'Failed to update timeline activity')
84
+ end
85
+
78
86
  def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
79
87
  response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
80
88
  return _cal_handle_response(response, 'Failed to get timeline activity')
@@ -97,6 +105,20 @@ module OpenC3
97
105
  return _cal_handle_response(response, 'Failed to delete timeline activity')
98
106
  end
99
107
 
108
+ def _build_activity_data(kind, start, stop, data)
109
+ kind = kind.to_s.downcase()
110
+ kinds = %w(command script reserve)
111
+ unless kinds.include?(kind)
112
+ raise "Unknown kind: #{kind}. Must be one of #{kinds.join(', ')}."
113
+ end
114
+ {
115
+ 'start' => start.to_datetime.iso8601,
116
+ 'stop' => stop.to_datetime.iso8601,
117
+ 'kind' => kind,
118
+ 'data' => data,
119
+ }
120
+ end
121
+
100
122
  # Helper method to handle the response
101
123
  def _cal_handle_response(response, error_message)
102
124
  return nil if response.nil?
@@ -15,6 +15,7 @@
15
15
  # This file may also be used under the terms of a commercial license
16
16
  # if purchased from OpenC3, Inc.
17
17
 
18
+ require 'json'
18
19
  require 'openc3/utilities/store'
19
20
 
20
21
  module OpenC3
@@ -154,22 +155,54 @@ module OpenC3
154
155
  end
155
156
 
156
157
  def extract_fields_from_check_text(text)
157
- split_string = text.split
158
- raise "ERROR: Check improperly specified: #{text}" if split_string.length < 3
158
+ target_name, packet_name, item_name, comparison = text.split(nil, 4) # Ruby: second split arg is max number of resultant elements
159
+ raise "ERROR: Check improperly specified: #{text}" if item_name.nil?
159
160
 
160
- target_name = split_string[0]
161
- packet_name = split_string[1]
162
- item_name = split_string[2]
163
- comparison_to_eval = nil
164
- return [target_name, packet_name, item_name, comparison_to_eval] if split_string.length == 3
165
- raise "ERROR: Check improperly specified: #{text}" if split_string.length < 4
161
+ # comparison is either nil, the comparison string, or an empty string.
162
+ # We need it to not be an empty string.
163
+ comparison = nil if comparison&.length == 0
164
+
165
+ operator, _ = comparison&.split(nil, 2)
166
+ raise "ERROR: Use '==' instead of '=' in #{text}" if operator == "="
167
+
168
+ return [target_name, packet_name, item_name, comparison]
169
+ end
170
+
171
+ # Splits `check()` comparison expressions, e.g. "== 'foo bar'" becomes ["==", "foo bar"]
172
+ def extract_operator_and_operand_from_comparison(comparison)
173
+ valid_operators = ["==", "!=", ">=", "<=", ">", "<", "in"]
166
174
 
167
- split_string = text.split(/ /) # Split on regex spaces to preserve spaces in comparison
168
- index = split_string.rindex(item_name)
169
- comparison_to_eval = split_string[(index + 1)..(split_string.length - 1)].join(" ")
170
- raise "ERROR: Use '==' instead of '=': #{text}" if split_string[3] == '='
175
+ operator, operand = comparison.split(nil, 2) # Ruby: second split arg is max number of resultant elements
171
176
 
172
- return [target_name, packet_name, item_name, comparison_to_eval]
177
+ if operand.nil?
178
+ # Don't allow operator without operand
179
+ raise "ERROR: Invalid comparison, must specify an operand: #{comparison}" if !operator.nil?
180
+ return [nil, nil]
181
+ end
182
+
183
+ raise "ERROR: Invalid operator: '#{operator}'" unless valid_operators.include?(operator)
184
+
185
+ # Handle string operand: remove surrounding double/single quotes
186
+ if operand.match?(/^(['"])(.*)\1$/m) # Starts with single or double quote, and ends with matching quote
187
+ operand = operand[1..-2]
188
+ return [operator, operand]
189
+ end
190
+
191
+ # Handle other operand types
192
+ if operand == "nil"
193
+ operand = nil
194
+ elsif operand == "false"
195
+ operand = false
196
+ elsif operand == "true"
197
+ operand = true
198
+ else
199
+ begin
200
+ operand = JSON.parse(operand)
201
+ rescue JSON::ParserError
202
+ raise "ERROR: Unable to parse operand: #{operand}"
203
+ end
204
+ end
205
+ return [operator, operand]
173
206
  end
174
207
  end
175
208
  end
@@ -44,11 +44,11 @@ require 'openc3/utilities/authentication'
44
44
  $api_server = nil
45
45
  $script_runner_api_server = nil
46
46
  $disconnect = false
47
- $openc3_scope = ENV['OPENC3_SCOPE'] || 'DEFAULT'
47
+ $openc3_scope = ENV.fetch('OPENC3_SCOPE', 'DEFAULT')
48
48
  $openc3_in_cluster = false
49
49
 
50
50
  saved_verbose = $VERBOSE
51
- $VERBOSE = false
51
+ $VERBOSE = nil
52
52
 
53
53
  module OpenC3
54
54
  module Script
@@ -37,9 +37,9 @@ module OpenC3
37
37
 
38
38
  def script_syntax_check(script, scope: $openc3_scope)
39
39
  endpoint = "/script-api/scripts/temp.rb/syntax"
40
- # Explicitly set the headers to plain/text so the request.body is set correctly
40
+ # Explicitly set the headers to text/plain so the request.body is set correctly
41
41
  headers = {
42
- 'Content-Type': 'plain/text',
42
+ 'Content-Type': 'text/plain',
43
43
  }
44
44
  response = $script_runner_api_server.request('post', endpoint, headers: headers, data: script, scope: scope)
45
45
  if response.nil? || response.status != 200
@@ -129,9 +129,9 @@ module OpenC3
129
129
 
130
130
  def script_instrumented(script, scope: $openc3_scope)
131
131
  endpoint = "/script-api/scripts/temp.rb/instrumented"
132
- # Explicitly set the headers to plain/text so the request.body is set correctly
132
+ # Explicitly set the headers to text/plain so the request.body is set correctly
133
133
  headers = {
134
- 'Content-Type': 'plain/text',
134
+ 'Content-Type': 'text/plain',
135
135
  }
136
136
  response = $script_runner_api_server.request('post', endpoint, headers: headers, data: script, scope: scope)
137
137
  if response.nil? || response.status != 200
@@ -40,9 +40,9 @@ module OpenC3
40
40
  # inject_tlm, set_tlm, override_tlm, and normalize_tlm are implemented here simply to add a puts
41
41
  # these methods modify the telemetry so the user should be notified in the Script Runner log messages
42
42
 
43
- def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, scope: $openc3_scope, token: $openc3_token)
44
- puts "inject_tlm(\"#{target_name}\", \"#{packet_name}\", #{item_hash}, type: #{type})"
45
- $api_server.method_missing(:inject_tlm, target_name, packet_name, item_hash, type: type, scope: scope, token: token)
43
+ def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, stored: false, scope: $openc3_scope, token: $openc3_token)
44
+ puts "inject_tlm(\"#{target_name}\", \"#{packet_name}\", #{item_hash}, type: #{type}, stored: #{stored})"
45
+ $api_server.method_missing(:inject_tlm, target_name, packet_name, item_hash, type: type, stored: stored, scope: scope, token: token)
46
46
  end
47
47
 
48
48
  def set_tlm(*args, type: :ALL, scope: $openc3_scope, token: $openc3_token)
@@ -50,34 +50,41 @@ module OpenC3
50
50
  start_time = Time.now
51
51
  while true
52
52
  message = read_message()
53
- if message
53
+ # Empty string is a normal end-of-stream signal when ActionCable / anycable-go
54
+ # closes the WS. Treat it the same as nil so consumer `while (resp = api.read)`
55
+ # loops exit cleanly instead of hitting JSON::ParserError on JSON.parse("").
56
+ return nil if message.nil? || message.empty?
57
+
58
+ begin
54
59
  json_hash = JSON.parse(message, allow_nan: true, create_additions: true)
55
- if ignore_protocol_messages
56
- type = json_hash['type']
57
- if type # ping, welcome, confirm_subscription, reject_subscription, disconnect
58
- if type == 'disconnect'
59
- if json_hash['reason'] == 'unauthorized'
60
- raise "Unauthorized"
61
- end
62
- end
63
- if type == 'reject_subscription'
64
- raise "Subscription Rejected"
65
- end
66
- if timeout
67
- end_time = Time.now
68
- if (end_time - start_time) > timeout
69
- raise Timeout::Error, "No Data Timeout"
70
- end
60
+ rescue JSON::ParserError
61
+ # Defense-in-depth: treat malformed frames as end-of-stream rather than crashing.
62
+ return nil
63
+ end
64
+ if ignore_protocol_messages
65
+ type = json_hash['type']
66
+ if type # ping, welcome, confirm_subscription, reject_subscription, disconnect
67
+ if type == 'disconnect'
68
+ if json_hash['reason'] == 'unauthorized'
69
+ raise "Unauthorized"
71
70
  end
72
- if defined? RunningScript and RunningScript.instance
73
- raise StopScript if RunningScript.instance.stop?
71
+ end
72
+ if type == 'reject_subscription'
73
+ raise "Subscription Rejected"
74
+ end
75
+ if timeout
76
+ end_time = Time.now
77
+ if (end_time - start_time) > timeout
78
+ raise Timeout::Error, "No Data Timeout"
74
79
  end
75
- next
76
80
  end
81
+ if defined? RunningScript and RunningScript.instance
82
+ raise StopScript if RunningScript.instance.stop?
83
+ end
84
+ next
77
85
  end
78
- return json_hash['message']
79
86
  end
80
- return message
87
+ return json_hash['message']
81
88
  end
82
89
  end
83
90
 
@@ -77,7 +77,8 @@ module OpenC3
77
77
  end
78
78
  end
79
79
 
80
- def self.setup_targets(target_names, base_dir, scope:)
80
+ # target_version can also be the actual hash used in the target_archives folder
81
+ def self.setup_targets(target_names, base_dir, target_version: 'current', scope:)
81
82
  # Nothing to do if there are no targets
82
83
  return if target_names.nil? or target_names.length == 0
83
84
  if @@instance.nil?
@@ -85,10 +86,15 @@ module OpenC3
85
86
  FileUtils.mkdir_p(targets_path)
86
87
  bucket = Bucket.getClient()
87
88
  target_names.each do |target_name|
89
+ # Remove any prior extraction so re-running setup_targets (e.g. after
90
+ # reset_instance! during reingest) starts from a clean slate. Without
91
+ # this, Zip::File#extract fails on files left behind by a previous run.
92
+ FileUtils.rm_rf("#{targets_path}/#{target_name}")
93
+
88
94
  # Retrieve bucket/targets/target_name/<TARGET>_current.zip
89
- zip_path = "#{targets_path}/#{target_name}_current.zip"
95
+ zip_path = "#{targets_path}/#{target_name}_#{target_version}.zip"
90
96
  FileUtils.mkdir_p(File.dirname(zip_path))
91
- bucket_key = "#{scope}/target_archives/#{target_name}/#{target_name}_current.zip"
97
+ bucket_key = "#{scope}/target_archives/#{target_name}/#{target_name}_#{target_version}.zip"
92
98
  Logger.info("Retrieving #{bucket_key} from targets bucket")
93
99
  bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, path: zip_path)
94
100
  Zip::File.open(zip_path) do |zip_file|
@@ -116,6 +122,17 @@ module OpenC3
116
122
  end
117
123
  end
118
124
 
125
+ # Clears the System singleton so the next call to setup_targets or
126
+ # instance rebuilds it. Intended for admin flows (e.g. reingest) that
127
+ # need to load a specific target_version distinct from whatever is
128
+ # currently loaded. Callers must hold an external lock if they need to
129
+ # protect other threads from observing a nil @@instance briefly.
130
+ def self.reset_instance!
131
+ @@instance_mutex.synchronize do
132
+ @@instance = nil
133
+ end
134
+ end
135
+
119
136
  # Get the singleton instance of System
120
137
  #
121
138
  # @param target_names [Array of target_names]
@@ -42,11 +42,13 @@ module OpenC3
42
42
  end
43
43
  msg_hash['json_data'] = JSON.generate(json_hash.as_json, allow_nan: true)
44
44
  msg_hash['extra'] = JSON.generate(packet.extra.as_json, allow_nan: true) if packet.extra
45
- EphemeralStoreQueued.write_topic(topic, msg_hash)
45
+ db_shard = Store.db_shard_for_target(packet.target_name, scope: scope)
46
+ EphemeralStoreQueued.instance(db_shard: db_shard).write_topic(topic, msg_hash)
46
47
  end
47
48
 
48
49
  def self.get_cmd_item(target_name, packet_name, param_name, type: :FORMATTED, scope: $openc3_scope)
49
- msg_id, msg_hash = Topic.get_newest_message("#{scope}__DECOMCMD__{#{target_name}}__#{packet_name}")
50
+ db_shard = Store.db_shard_for_target(target_name, scope: scope)
51
+ msg_id, msg_hash = Topic.get_newest_message("#{scope}__DECOMCMD__{#{target_name}}__#{packet_name}", db_shard: db_shard)
50
52
  if msg_id
51
53
  if param_name == 'RECEIVED_COUNT'
52
54
  msg_hash['received_count'].to_i
@@ -33,7 +33,8 @@ module OpenC3
33
33
  received_count: packet.received_count,
34
34
  stored: packet.stored.to_s,
35
35
  buffer: packet.buffer(false) }
36
- EphemeralStoreQueued.write_topic(topic, msg_hash)
36
+ db_shard = Store.db_shard_for_target(packet.target_name, scope: scope)
37
+ EphemeralStoreQueued.instance(db_shard: db_shard).write_topic(topic, msg_hash)
37
38
  end
38
39
 
39
40
  # @param command [Hash] Command hash structure read to be written to a topic
@@ -45,20 +46,22 @@ module OpenC3
45
46
  command['cmd_params'] = JSON.generate(command['cmd_params'].as_json, allow_nan: true)
46
47
  OpenC3.inject_context(command)
47
48
 
49
+ db_shard = Store.db_shard_for_target(command['target_name'], scope: scope)
50
+
48
51
  # Fire-and-forget mode: skip ACK waiting when timeout <= 0
49
52
  if timeout <= 0
50
- Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100)
53
+ Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100, db_shard: db_shard)
51
54
  command["cmd_params"] = cmd_params # Restore the original cmd_params Hash
52
55
  return command
53
56
  end
54
57
 
55
58
  ack_topic = "{#{scope}__ACKCMD}TARGET__#{command['target_name']}"
56
- Topic.update_topic_offsets([ack_topic])
57
- cmd_id = Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100)
59
+ Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
60
+ cmd_id = Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100, db_shard: db_shard)
58
61
  command["cmd_params"] = cmd_params # Restore the original cmd_params Hash
59
62
  time = Time.now
60
63
  while (Time.now - time) < timeout
61
- Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
64
+ Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
62
65
  if msg_hash["id"] == cmd_id
63
66
  if msg_hash["result"] == "SUCCESS"
64
67
  return command
@@ -26,18 +26,19 @@ module OpenC3
26
26
  # DecomMicroservice is listening to the DECOMINTERFACE topic and is responsible
27
27
  # for actually building the command. This was deliberate to allow this to work
28
28
  # with or without an interface.
29
+ db_shard = Store.db_shard_for_target(target_name, scope: scope)
29
30
  ack_topic = "{#{scope}__ACKCMD}TARGET__#{target_name}"
30
- Topic.update_topic_offsets([ack_topic])
31
+ Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
31
32
  decom_id = Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
32
- { 'build_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100)
33
+ { 'build_cmd' => JSON.generate(data, allow_nan: true) }, '*', 100, db_shard: db_shard)
33
34
  time = Time.now
34
35
  while (Time.now - time) < timeout
35
- Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
36
+ Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
36
37
  if msg_hash["id"] == decom_id
37
38
  if msg_hash["result"] == "SUCCESS"
38
39
  return msg_hash
39
40
  else
40
- raise msg_hash["message"]
41
+ raise msg_hash["result"]
41
42
  end
42
43
  end
43
44
  end
@@ -45,14 +46,32 @@ module OpenC3
45
46
  raise "Timeout of #{timeout}s waiting for cmd ack. Does target '#{target_name}' exist?"
46
47
  end
47
48
 
48
- def self.inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, scope:)
49
+ def self.inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, stored: false, timeout: 5, scope:)
49
50
  data = {}
50
51
  data['target_name'] = target_name.to_s.upcase
51
52
  data['packet_name'] = packet_name.to_s.upcase
52
53
  data['item_hash'] = item_hash
53
54
  data['type'] = type
54
- Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
55
- { 'inject_tlm' => JSON.generate(data, allow_nan: true) }, '*', 100)
55
+
56
+ db_shard = Store.db_shard_for_target(target_name, scope: scope)
57
+ data['stored'] = stored
58
+ ack_topic = "{#{scope}__ACKCMD}TARGET__#{target_name}"
59
+ Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
60
+ decom_id = Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
61
+ { 'inject_tlm' => JSON.generate(data, allow_nan: true) }, '*', 100, db_shard: db_shard)
62
+ time = Time.now
63
+ while (Time.now - time) < timeout
64
+ Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
65
+ if msg_hash["id"] == decom_id
66
+ if msg_hash["result"] == "SUCCESS"
67
+ return
68
+ else
69
+ raise msg_hash["result"]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ raise "Timeout of #{timeout}s waiting for cmd ack. Does target '#{target_name}' exist?"
56
75
  end
57
76
 
58
77
  def self.get_tlm_buffer(target_name, packet_name, timeout: 5, scope:)
@@ -61,13 +80,14 @@ module OpenC3
61
80
  data['packet_name'] = packet_name.to_s.upcase
62
81
  # DecomMicroservice is listening to the DECOMINTERFACE topic and has
63
82
  # the most recent decommed packets including subpackets
83
+ db_shard = Store.db_shard_for_target(target_name, scope: scope)
64
84
  ack_topic = "{#{scope}__ACKCMD}TARGET__#{target_name}"
65
- Topic.update_topic_offsets([ack_topic])
85
+ Topic.update_topic_offsets([ack_topic], db_shard: db_shard)
66
86
  decom_id = Topic.write_topic("#{scope}__DECOMINTERFACE__{#{target_name}}",
67
- { 'get_tlm_buffer' => JSON.generate(data, allow_nan: true) }, '*', 100)
87
+ { 'get_tlm_buffer' => JSON.generate(data, allow_nan: true) }, '*', 100, db_shard: db_shard)
68
88
  time = Time.now
69
89
  while (Time.now - time) < timeout
70
- Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
90
+ Topic.read_topics([ack_topic], db_shard: db_shard) do |_topic, _msg_id, msg_hash, _redis|
71
91
  if msg_hash["id"] == decom_id
72
92
  if msg_hash["result"] == "SUCCESS"
73
93
  msg_hash["stored"] = ConfigParser.handle_true_false(msg_hash["stored"])
@@ -77,7 +97,7 @@ module OpenC3
77
97
  end
78
98
  return msg_hash
79
99
  else
80
- raise msg_hash["message"]
100
+ raise msg_hash["result"]
81
101
  end
82
102
  end
83
103
  end