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
@@ -14,12 +14,14 @@
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
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
+ require 'pg'
24
+ require 'thread'
23
25
  require 'openc3/utilities/store'
24
26
  require 'openc3/utilities/store_queued'
25
27
  require 'openc3/models/target_model'
@@ -28,6 +30,8 @@ module OpenC3
28
30
  class CvtModel
29
31
  @@packet_cache = {}
30
32
  @@override_cache = {}
33
+ @@conn = nil
34
+ @@conn_mutex = Mutex.new
31
35
 
32
36
  VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
33
37
  def self.build_json_from_packet(packet)
@@ -117,22 +121,149 @@ module OpenC3
117
121
  end
118
122
  end
119
123
 
124
+ def self.tsdb_lookup(items, start_time:, end_time: nil)
125
+ tables = {}
126
+ names = []
127
+ nil_count = 0
128
+ items.each do |item|
129
+ target_name, packet_name, item_name, value_type, limits = item
130
+ # They will all be nil when item is a nil value
131
+ # A nil value indicates a value that does not exist as returned by get_tlm_available
132
+ if item_name.nil?
133
+ # We know timestamp always exists so we can use it to fill in the nil value
134
+ names << "timestamp as __nil#{nil_count}"
135
+ nil_count += 1
136
+ next
137
+ end
138
+ # See https://questdb.com/docs/reference/api/ilp/advanced-settings/#name-restrictions
139
+ table_name = "#{target_name}__#{packet_name}".gsub(/[?,'"\/:\)\(\+\*\%~]/, '_')
140
+ tables[table_name] = 1
141
+ index = tables.find_index {|k,v| k == table_name }
142
+ # See https://questdb.com/docs/reference/api/ilp/advanced-settings/#name-restrictions
143
+ # NOTE: Semicolon added as it appears invalid
144
+ item_name = item_name.gsub(/[?\.,'"\\\/:\)\(\+\-\*\%~;]/, '_')
145
+ case value_type
146
+ when 'WITH_UNITS'
147
+ names << "\"T#{index}.#{item_name}__U\""
148
+ when 'FORMATTED'
149
+ names << "\"T#{index}.#{item_name}__F\""
150
+ when 'CONVERTED'
151
+ names << "\"T#{index}.#{item_name}__C\""
152
+ else
153
+ names << "\"T#{index}.#{item_name}\""
154
+ end
155
+ if limits
156
+ names << "\"T#{index}.#{item_name}__L\""
157
+ end
158
+ end
159
+
160
+ # Build the SQL query
161
+ query = "SELECT #{names.join(", ")} FROM "
162
+ tables.each_with_index do |(table_name, _), index|
163
+ if index == 0
164
+ query += "#{table_name} as T#{index} "
165
+ else
166
+ query += "ASOF JOIN #{table_name} as T#{index} "
167
+ end
168
+ end
169
+ if start_time && !end_time
170
+ query += "WHERE T0.timestamp < '#{start_time}' LIMIT -1"
171
+ elsif start_time && end_time
172
+ query += "WHERE T0.timestamp >= '#{start_time}' AND T0.timestamp < '#{end_time}'"
173
+ end
174
+
175
+ retry_count = 0
176
+ begin
177
+ @@conn_mutex.synchronize do
178
+ @@conn ||= PG::Connection.new(host: ENV['OPENC3_TSDB_HOSTNAME'],
179
+ port: ENV['OPENC3_TSDB_QUERY_PORT'],
180
+ user: ENV['OPENC3_TSDB_USERNAME'],
181
+ password: ENV['OPENC3_TSDB_PASSWORD'],
182
+ dbname: 'qdb')
183
+ # Default connection is all strings but we want to map to the correct types
184
+ if @@conn.type_map_for_results.is_a? PG::TypeMapAllStrings
185
+ # TODO: This doesn't seem to be round tripping UINT64 correctly
186
+ # Try playback with P_2.2,2 and P(:6;): from the DEMO
187
+ @@conn.type_map_for_results = PG::BasicTypeMapForResults.new @@conn
188
+ end
189
+
190
+ result = @@conn.exec(query)
191
+ if result.nil? or result.ntuples == 0
192
+ return {}
193
+ else
194
+ data = []
195
+ # Build up a results set that is an array of arrays
196
+ # Each nested array is a set of 2 items: [value, limits state]
197
+ # If the item does not have limits the limits state is nil
198
+ result.each_with_index do |tuples, index|
199
+ data[index] ||= []
200
+ row_index = 0
201
+ tuples.each do |tuple|
202
+ if tuple[0].include?("__L")
203
+ data[index][row_index - 1][1] = tuple[1]
204
+ elsif tuple[0] =~ /^__nil/
205
+ data[index][row_index] = [nil, nil]
206
+ row_index += 1
207
+ else
208
+ data[index][row_index] = [tuple[1], nil]
209
+ row_index += 1
210
+ end
211
+ end
212
+ end
213
+ # If we only have one row then we return a single array
214
+ if result.ntuples == 1
215
+ data = data[0]
216
+ end
217
+ return data
218
+ end
219
+ end
220
+ rescue IOError, PG::Error => e
221
+ # Retry the query because various errors can occur that are recoverable
222
+ retry_count += 1
223
+ if retry_count > 4
224
+ # After the 5th retry just raise the error
225
+ raise "Error querying QuestDB: #{e.message}"
226
+ end
227
+ Logger.warn("QuestDB: Retrying due to error: #{e.message}")
228
+ Logger.warn("QuestDB: Last query: #{query}") # Log the last query for debugging
229
+ @@conn_mutex.synchronize do
230
+ if @@conn and !@@conn.finished?
231
+ @@conn.finish()
232
+ end
233
+ @@conn = nil # Force the new connection
234
+ end
235
+ sleep 0.1
236
+ retry
237
+ end
238
+ end
239
+
120
240
  # Return all item values and limit state from the CVT
121
241
  #
122
242
  # @param items [Array<String>] Items to return. Must be formatted as TGT__PKT__ITEM__TYPE
123
243
  # @param stale_time [Integer] Time in seconds from Time.now that value will be marked stale
124
244
  # @return [Array] Array of values
125
- def self.get_tlm_values(items, stale_time: 30, cache_timeout: nil, scope: $openc3_scope)
245
+ def self.get_tlm_values(items, stale_time: 30, cache_timeout: nil, start_time: nil, end_time: nil, scope: $openc3_scope)
126
246
  now = Time.now
127
247
  results = []
128
248
  lookups = []
129
249
  packet_lookup = {}
130
250
  overrides = {}
251
+
252
+ # If a start_time is passed we're doing a QuestDB lookup and directly return the results
253
+ # TODO: This currently does NOT support the override values
254
+ if start_time
255
+ return tsdb_lookup(items, start_time: start_time, end_time: end_time)
256
+ end
257
+
131
258
  # First generate a lookup hash of all the items represented so we can query the CVT
132
259
  items.each { |item| _parse_item(now, lookups, overrides, item, cache_timeout: cache_timeout, scope: scope) }
133
260
 
134
261
  now = now.to_f
135
262
  lookups.each do |target_packet_key, target_name, packet_name, value_keys|
263
+ if target_packet_key.nil?
264
+ results << [nil, nil]
265
+ next
266
+ end
136
267
  unless packet_lookup[target_packet_key]
137
268
  packet_lookup[target_packet_key] = get(target_name: target_name, packet_name: packet_name, cache_timeout: cache_timeout, scope: scope)
138
269
  end
@@ -161,7 +292,8 @@ module OpenC3
161
292
  if hash.key?(value_keys[-1])
162
293
  item_result[1] = nil
163
294
  else
164
- raise "Item '#{target_name} #{packet_name} #{value_keys[-1]}' does not exist"
295
+ item_result[0] = nil
296
+ item_result[1] = nil
165
297
  end
166
298
  end
167
299
  end
@@ -335,6 +467,11 @@ module OpenC3
335
467
  # return an ordered array of hash with keys
336
468
  def self._parse_item(now, lookups, overrides, item, cache_timeout:, scope:)
337
469
  target_name, packet_name, item_name, value_type = item
470
+ # They will all be nil when item is a nil value
471
+ if item_name.nil?
472
+ lookups << nil
473
+ return
474
+ end
338
475
 
339
476
  # We build lookup keys by including all the less formatted types to gracefully degrade lookups
340
477
  # This allows the user to specify WITH_UNITS and if there is no conversions it will simply return the RAW value
@@ -55,6 +55,7 @@ module OpenC3
55
55
  attr_accessor :variables
56
56
  attr_accessor :plugin_txt_lines
57
57
  attr_accessor :needs_dependencies
58
+ attr_accessor :store_id
58
59
 
59
60
  # NOTE: The following three class methods are used by the ModelController
60
61
  # and are reimplemented to enable various Model class methods to work
@@ -72,7 +73,7 @@ module OpenC3
72
73
 
73
74
  # Called by the PluginsController to parse the plugin variables
74
75
  # Doesn't actually create the plugin during the phase
75
- def self.install_phase1(gem_file_path, existing_variables: nil, existing_plugin_txt_lines: nil, process_existing: false, scope:, validate_only: false)
76
+ def self.install_phase1(gem_file_path, existing_variables: nil, existing_plugin_txt_lines: nil, store_id: nil, process_existing: false, scope:, validate_only: false)
76
77
  gem_name = File.basename(gem_file_path).split("__")[0]
77
78
 
78
79
  temp_dir = Dir.mktmpdir
@@ -130,7 +131,8 @@ module OpenC3
130
131
  end
131
132
  end
132
133
 
133
- model = PluginModel.new(name: gem_name, variables: variables, plugin_txt_lines: plugin_txt_lines, scope: scope)
134
+ store_id = Integer(store_id) if store_id
135
+ model = PluginModel.new(name: gem_name, variables: variables, plugin_txt_lines: plugin_txt_lines, store_id: store_id, scope: scope)
134
136
  result = model.as_json(:allow_nan => true)
135
137
  result['existing_plugin_txt_lines'] = existing_plugin_txt_lines if existing_plugin_txt_lines and not process_existing and existing_plugin_txt_lines != result['plugin_txt_lines']
136
138
  return result
@@ -299,6 +301,7 @@ module OpenC3
299
301
  variables: {},
300
302
  plugin_txt_lines: [],
301
303
  needs_dependencies: false,
304
+ store_id: nil,
302
305
  updated_at: nil,
303
306
  scope:
304
307
  )
@@ -306,6 +309,7 @@ module OpenC3
306
309
  @variables = variables
307
310
  @plugin_txt_lines = plugin_txt_lines
308
311
  @needs_dependencies = ConfigParser.handle_true_false(needs_dependencies)
312
+ @store_id = store_id
309
313
  end
310
314
 
311
315
  def create(update: false, force: false, queued: false)
@@ -319,6 +323,7 @@ module OpenC3
319
323
  'variables' => @variables,
320
324
  'plugin_txt_lines' => @plugin_txt_lines,
321
325
  'needs_dependencies' => @needs_dependencies,
326
+ 'store_id' => @store_id,
322
327
  'updated_at' => @updated_at
323
328
  }
324
329
  end
@@ -0,0 +1,70 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2025 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
+ require 'openc3/utilities/store'
21
+
22
+ module OpenC3
23
+ class PluginStoreModel < Model
24
+ PRIMARY_KEY = 'openc3_plugin_store'
25
+
26
+ def self.set(plugin_store_data)
27
+ Store.set(PRIMARY_KEY, plugin_store_data)
28
+ end
29
+
30
+ def self.all()
31
+ Store.get(PRIMARY_KEY)
32
+ end
33
+
34
+ def self.plugin_store_error(message)
35
+ Store.set(PRIMARY_KEY, [{
36
+ date: Time.now.utc.iso8601,
37
+ title: 'Plugin Store Error',
38
+ body: message,
39
+ error: true,
40
+ }].to_json)
41
+ end
42
+
43
+ def self.get_by_id(id)
44
+ plugins = JSON.parse(all()) rescue []
45
+ plugins.find { |plugin| plugin["id"] == Integer(id) }
46
+ end
47
+
48
+ def self.update
49
+ setting = SettingModel.get(name: 'store_url', scope: 'DEFAULT')
50
+ store_url = setting['data'] if setting
51
+ store_url = 'https://store.openc3.com' if store_url.nil? or store_url.strip.empty?
52
+ conn = Faraday.new(
53
+ url: store_url,
54
+ )
55
+ response = conn.get('/cosmos_plugins/json')
56
+ if response.success?
57
+ self.set(response.body)
58
+ else
59
+ self.plugin_store_error("Error contacting plugin store at #{store_url} (status: #{response.status})")
60
+ end
61
+ rescue Exception => e
62
+ self.plugin_store_error("Error contacting plugin store at #{store_url}. #{e.message})")
63
+ end
64
+
65
+ def self.ensure_exists
66
+ plugins = self.all()
67
+ self.update() if plugins.nil? or plugins.length.zero? or plugins[0]['error']
68
+ end
69
+ end
70
+ end
@@ -1088,6 +1088,25 @@ module OpenC3
1088
1088
  Logger.info "Configured microservice #{microservice_name}"
1089
1089
  end
1090
1090
 
1091
+ def deploy_tsdb_microservice(gem_path, variables, topics, instance = nil, parent = nil)
1092
+ microservice_name = "#{@scope}__TSDB#{instance}__#{@name}"
1093
+ microservice = MicroserviceModel.new(
1094
+ name: microservice_name,
1095
+ folder_name: @folder_name,
1096
+ cmd: ["python", "quest_microservice.py", microservice_name],
1097
+ work_dir: "/openc3/python/openc3/microservices",
1098
+ topics: topics,
1099
+ plugin: @plugin,
1100
+ parent: nil,
1101
+ needs_dependencies: @needs_dependencies,
1102
+ shard: @shard,
1103
+ scope: @scope
1104
+ )
1105
+ microservice.create
1106
+ microservice.deploy(gem_path, variables)
1107
+ Logger.info "Configured microservice #{microservice_name}"
1108
+ end
1109
+
1091
1110
  def deploy_reducer_microservice(gem_path, variables, topics, instance = nil, parent = nil)
1092
1111
  microservice_name = "#{@scope}__REDUCER#{instance}__#{@name}"
1093
1112
  microservice = MicroserviceModel.new(
@@ -1250,6 +1269,13 @@ module OpenC3
1250
1269
  deploy_decom_microservice(system.targets[@name], gem_path, variables, topics, instance, parent)
1251
1270
  end
1252
1271
 
1272
+ # TSDB Microservice
1273
+ if ENV['OPENC3_TSDB_HOSTNAME'] and ENV['OPENC3_TSDB_QUERY_PORT'] and ENV['OPENC3_TSDB_INGEST_PORT'] and ENV['OPENC3_TSDB_USERNAME'] and ENV['OPENC3_TSDB_PASSWORD']
1274
+ deploy_target_microservices('TSDB', decom_topic_list, "#{@scope}__DECOM__{#{@name}}") do |topics, instance, parent|
1275
+ deploy_tsdb_microservice(gem_path, variables, topics, instance, parent)
1276
+ end
1277
+ end
1278
+
1253
1279
  # Reducer Microservice
1254
1280
  unless @reducer_disable
1255
1281
  # TODO: Does Reducer even need a topic list?
@@ -184,7 +184,7 @@ module OpenC3
184
184
  end
185
185
  end
186
186
 
187
- if @url and !@url.start_with?('/') and !@url.start_with?('http')
187
+ if @url and !@url.start_with?('/') and @url !~ URI::regexp
188
188
  raise "URL must be a full URL (http://domain.com/path) or a relative path (/path)"
189
189
  end
190
190
 
@@ -334,7 +334,7 @@ module OpenC3
334
334
  synchronize() do
335
335
  begin
336
336
  internal_buffer_equals(buffer)
337
- rescue RuntimeError => e
337
+ rescue RuntimeError
338
338
  Logger.instance.error "#{@target_name} #{@packet_name} received with actual packet length of #{buffer.length} but defined length of #{@defined_length}"
339
339
  end
340
340
  @read_conversion_cache.clear if @read_conversion_cache
@@ -1315,7 +1315,7 @@ module OpenC3
1315
1315
 
1316
1316
  begin
1317
1317
  current_value = read(item.name, :RAW)
1318
-
1318
+
1319
1319
  case current_value
1320
1320
  when Array
1321
1321
  # For arrays, create a new array of zeros with the same size
@@ -1325,7 +1325,7 @@ module OpenC3
1325
1325
  when :FLOAT
1326
1326
  obfuscated_value = Array.new(current_value.size, 0.0)
1327
1327
  when :STRING, :BLOCK
1328
- obfuscated_value = Array.new(current_value.size) { |i|
1328
+ obfuscated_value = Array.new(current_value.size) { |i|
1329
1329
  "\x00" * current_value[i].length if current_value[i]
1330
1330
  }
1331
1331
  else
@@ -80,7 +80,13 @@ module OpenC3
80
80
  if data_type == :STRING || data_type == :BLOCK
81
81
  @parser.parameters[1]
82
82
  else
83
- @parser.parameters[1].convert_to_value
83
+ value = @parser.parameters[1].convert_to_value
84
+ # Check if the value is a string, which indicates an error in parsing
85
+ # except for 'ANY' which is a valid state value
86
+ if value.is_a?(String) and value != "ANY"
87
+ raise @parser.error("Invalid state value #{value} for data type #{data_type}.", @usage)
88
+ end
89
+ return value
84
90
  end
85
91
  end
86
92
 
@@ -603,7 +603,7 @@ module OpenC3
603
603
  if item.variable_bit_size
604
604
  # Bit size is determined by length field
605
605
  length_value = self.read(item.variable_bit_size['length_item_name'], :CONVERTED)
606
- if item.data_type == :INT or item.data_type == :UINT and not item.original_array_size
606
+ if (item.data_type == :INT or item.data_type == :UINT) and not item.original_array_size
607
607
  case length_value
608
608
  when 0
609
609
  return 6
@@ -641,7 +641,14 @@ module OpenC3
641
641
  # Calculate the actual current size of this variable length item
642
642
  new_bit_size = calculate_total_bit_size(item)
643
643
 
644
- if item.original_bit_size != new_bit_size
644
+ if item.original_array_size
645
+ # Array size has changed from original - so we need to adjust everything after this item
646
+ # This includes items that may have the same bit_offset as the variable length item because it
647
+ # started out at zero bit_size
648
+ if item.original_array_size != new_bit_size
649
+ adjustment += (new_bit_size - item.original_array_size)
650
+ end
651
+ elsif item.original_bit_size != new_bit_size
645
652
  # Bit size has changed from original - so we need to adjust everything after this item
646
653
  # This includes items that may have the same bit_offset as the variable length item because it
647
654
  # started out at zero bit_size
@@ -25,7 +25,7 @@ module OpenC3
25
25
 
26
26
  def list_timelines(scope: $openc3_scope)
27
27
  response = $api_server.request('get', "/openc3-api/timeline", scope: scope)
28
- return _handle_response(response, 'Failed to list timelines')
28
+ return _cal_handle_response(response, 'Failed to list timelines')
29
29
  end
30
30
 
31
31
  def create_timeline(name, color: nil, scope: $openc3_scope)
@@ -33,19 +33,19 @@ module OpenC3
33
33
  data['name'] = name
34
34
  data['color'] = color if color
35
35
  response = $api_server.request('post', "/openc3-api/timeline", data: data, json: true, scope: scope)
36
- return _handle_response(response, 'Failed to create timeline')
36
+ return _cal_handle_response(response, 'Failed to create timeline')
37
37
  end
38
38
 
39
39
  def get_timeline(name, scope: $openc3_scope)
40
40
  response = $api_server.request('get', "/openc3-api/timeline/#{name}", scope: scope)
41
- return _handle_response(response, 'Failed to get timeline')
41
+ return _cal_handle_response(response, 'Failed to get timeline')
42
42
  end
43
43
 
44
44
  def set_timeline_color(name, color, scope: $openc3_scope)
45
45
  post_data = {}
46
46
  post_data['color'] = color
47
47
  response = $api_server.request('post', "/openc3-api/timeline/#{name}/color", data: post_data, json: true, scope: scope)
48
- return _handle_response(response, 'Failed to set timeline color')
48
+ return _cal_handle_response(response, 'Failed to set timeline color')
49
49
  end
50
50
 
51
51
  def delete_timeline(name, force: false, scope: $openc3_scope)
@@ -54,7 +54,7 @@ module OpenC3
54
54
  url += "?force=true"
55
55
  end
56
56
  response = $api_server.request('delete', url, scope: scope)
57
- return _handle_response(response, 'Failed to delete timeline')
57
+ return _cal_handle_response(response, 'Failed to delete timeline')
58
58
  end
59
59
 
60
60
  def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
@@ -69,12 +69,12 @@ module OpenC3
69
69
  post_data['kind'] = kind
70
70
  post_data['data'] = data
71
71
  response = $api_server.request('post', "/openc3-api/timeline/#{name}/activities", data: post_data, json: true, scope: scope)
72
- return _handle_response(response, 'Failed to create timeline activity')
72
+ return _cal_handle_response(response, 'Failed to create timeline activity')
73
73
  end
74
74
 
75
75
  def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
76
76
  response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
77
- return _handle_response(response, 'Failed to get timeline activity')
77
+ return _cal_handle_response(response, 'Failed to get timeline activity')
78
78
  end
79
79
 
80
80
  def get_timeline_activities(name, start: nil, stop: nil, limit: nil, scope: $openc3_scope)
@@ -86,16 +86,16 @@ module OpenC3
86
86
  url += "?limit=#{limit}"
87
87
  end
88
88
  response = $api_server.request('get', url, scope: scope)
89
- return _handle_response(response, 'Failed to get timeline activities')
89
+ return _cal_handle_response(response, 'Failed to get timeline activities')
90
90
  end
91
91
 
92
92
  def delete_timeline_activity(name, start, uuid, scope: $openc3_scope)
93
93
  response = $api_server.request('delete', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
94
- return _handle_response(response, 'Failed to delete timeline activity')
94
+ return _cal_handle_response(response, 'Failed to delete timeline activity')
95
95
  end
96
96
 
97
97
  # Helper method to handle the response
98
- def _handle_response(response, error_message)
98
+ def _cal_handle_response(response, error_message)
99
99
  return nil if response.nil?
100
100
  if response.status >= 400
101
101
  result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
@@ -115,7 +115,7 @@ module OpenC3
115
115
  end
116
116
  end
117
117
  _log_cmd(command, raw, no_range, no_hazardous)
118
- end
118
+ end
119
119
 
120
120
  # Send the command and log the results
121
121
  # This method signature has to include the keyword params present in cmd_api.rb _cmd_implementation()
@@ -71,7 +71,7 @@ module OpenC3
71
71
  end
72
72
  end
73
73
 
74
- def script_run(filename, disconnect: false, environment: nil, scope: $openc3_scope)
74
+ def script_run(filename, disconnect: false, environment: nil, suite_runner: nil, scope: $openc3_scope)
75
75
  if disconnect
76
76
  endpoint = "/script-api/scripts/#{filename}/run/disconnect"
77
77
  else
@@ -87,8 +87,13 @@ module OpenC3
87
87
  else
88
88
  env_data = []
89
89
  end
90
+ data = { environment: env_data }
91
+ if suite_runner
92
+ # TODO 7.0: Should suiteRunner be snake case?
93
+ data['suiteRunner'] = suite_runner
94
+ end
90
95
  # NOTE: json: true causes json_api_object to JSON generate and set the Content-Type to json
91
- response = $script_runner_api_server.request('post', endpoint, json: true, data: { environment: env_data }, scope: scope)
96
+ response = $script_runner_api_server.request('post', endpoint, json: true, data: data, scope: scope)
92
97
  if response.nil? || response.status != 200
93
98
  _script_response_error(response, "Failed to run #{filename}", scope: scope)
94
99
  else
@@ -24,7 +24,7 @@ module OpenC3
24
24
  post_data = {}
25
25
  post_data['definition'] = definition
26
26
  response = $api_server.request('post', '/openc3-api/tables/generate', json: true, data: post_data, scope: scope)
27
- return _handle_response(response, 'Failed to create binary')
27
+ return _tables_handle_response(response, 'Failed to create binary')
28
28
  end
29
29
 
30
30
  def table_create_report(filename, definition, table_name: nil, scope: $openc3_scope)
@@ -33,11 +33,11 @@ module OpenC3
33
33
  post_data['definition'] = definition
34
34
  post_data['table_name'] = table_name if table_name
35
35
  response = $api_server.request('post', '/openc3-api/tables/report', json: true, data: post_data, scope: scope)
36
- return _handle_response(response, 'Failed to create report')
36
+ return _tables_handle_response(response, 'Failed to create report')
37
37
  end
38
38
 
39
39
  # Helper method to handle the response
40
- def _handle_response(response, error_message)
40
+ def _tables_handle_response(response, error_message)
41
41
  return nil if response.nil?
42
42
  if response.status >= 400
43
43
  result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
@@ -50,7 +50,7 @@ rescue LoadError
50
50
  end
51
51
 
52
52
  def user_info(_token)
53
- {} # EE does stuff here
53
+ {} # Enterprise does stuff here
54
54
  end
55
55
  end
56
56
  end
@@ -29,7 +29,7 @@ module OpenC3
29
29
  user = {}
30
30
  end
31
31
  username = user['username']
32
- # Core username (EE has the actual username)
32
+ # Core username (Enterprise has the actual username)
33
33
  username ||= 'anonymous'
34
34
  end
35
35
  end
@@ -179,7 +179,7 @@ module OpenC3
179
179
  data = { time: time.to_nsec_from_epoch, '@timestamp' => time.iso8601(6), level: log_level }
180
180
  data[:microservice_name] = @microservice_name if @microservice_name
181
181
  data[:detail] = @detail_string if @detail_string
182
- data[:user] = user if user # EE: If a user is passed, put its name. Don't include user data if no user was passed.
182
+ data[:user] = user if user # Enterprise: If a user is passed, put its name. Don't include user data if no user was passed.
183
183
  if block_given?
184
184
  message = yield
185
185
  end
@@ -771,7 +771,11 @@ class RunningScript
771
771
  def run
772
772
  if @script_status.suite_runner
773
773
  @script_status.suite_runner = JSON.parse(@script_status.suite_runner, :allow_nan => true, :create_additions => true) # Convert to hash
774
- parse_options(@script_status.suite_runner['options'])
774
+ if @script_status.suite_runner['options']
775
+ parse_options(@script_status.suite_runner['options'])
776
+ else
777
+ parse_options({}) # Set default options
778
+ end
775
779
  if @script_status.suite_runner['script']
776
780
  run_text("OpenC3::SuiteRunner.start(#{@script_status.suite_runner['suite']}, #{@script_status.suite_runner['group']}, '#{@script_status.suite_runner['script']}')", initial_filename: "SCRIPTRUNNER")
777
781
  elsif script_status.suite_runner['group']
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- OPENC3_VERSION = '6.6.0'
3
+ OPENC3_VERSION = '6.7.0'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '6'
7
- MINOR = '6'
7
+ MINOR = '7'
8
8
  PATCH = '0'
9
9
  OTHER = ''
10
- BUILD = 'ceee2aabacbdfe57fe8b5de3580358db2ae3a19a'
10
+ BUILD = '6c4726a21dd004109cf840f83a99f5a8614cfbe3'
11
11
  end
12
- VERSION = '6.6.0'
13
- GEM_VERSION = '6.6.0'
12
+ VERSION = '6.7.0'
13
+ GEM_VERSION = '6.7.0'
14
14
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= tool_name %>",
3
- "version": "6.6.0",
3
+ "version": "6.7.0",
4
4
  "scripts": {
5
5
  "ng": "ng",
6
6
  "start": "ng serve",
@@ -23,7 +23,7 @@
23
23
  "@angular/platform-browser-dynamic": "^18.2.6",
24
24
  "@angular/router": "^18.2.6",
25
25
  "@astrouxds/astro-web-components": "^7.24.0",
26
- "@openc3/js-common": "6.6.0",
26
+ "@openc3/js-common": "6.7.0",
27
27
  "rxjs": "~7.8.0",
28
28
  "single-spa": "^5.9.5",
29
29
  "single-spa-angular": "^9.2.0",
@@ -16,7 +16,7 @@
16
16
  "@emotion/react": "^11.13.3",
17
17
  "@emotion/styled": "^11.11.0",
18
18
  "@mui/material": "^6.1.1",
19
- "@openc3/js-common": "6.6.0",
19
+ "@openc3/js-common": "6.7.0",
20
20
  "react": "^18.2.0",
21
21
  "react-dom": "^18.2.0",
22
22
  "single-spa-react": "^5.1.4"