openc3 7.0.0.pre.rc3 → 7.0.1

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +58 -10
  3. data/bin/pipinstall +38 -6
  4. data/data/config/command_modifiers.yaml +1 -0
  5. data/data/config/interface_modifiers.yaml +1 -1
  6. data/data/config/item_modifiers.yaml +20 -7
  7. data/data/config/table_parameter_modifiers.yaml +3 -1
  8. data/data/config/telemetry.yaml +1 -1
  9. data/lib/openc3/accessors/json_accessor.rb +1 -1
  10. data/lib/openc3/accessors/template_accessor.rb +9 -0
  11. data/lib/openc3/api/tlm_api.rb +3 -3
  12. data/lib/openc3/config/config_parser.rb +4 -4
  13. data/lib/openc3/conversions/conversion.rb +3 -3
  14. data/lib/openc3/core_ext/faraday.rb +4 -0
  15. data/lib/openc3/interfaces/interface.rb +1 -6
  16. data/lib/openc3/logs/log_writer.rb +24 -6
  17. data/lib/openc3/logs/packet_log_writer.rb +1 -4
  18. data/lib/openc3/logs/stream_log_pair.rb +11 -4
  19. data/lib/openc3/logs/text_log_writer.rb +1 -4
  20. data/lib/openc3/microservices/decom_microservice.rb +1 -1
  21. data/lib/openc3/microservices/interface_decom_common.rb +22 -8
  22. data/lib/openc3/microservices/interface_microservice.rb +14 -3
  23. data/lib/openc3/microservices/log_microservice.rb +7 -2
  24. data/lib/openc3/microservices/microservice.rb +10 -4
  25. data/lib/openc3/microservices/queue_microservice.rb +3 -0
  26. data/lib/openc3/microservices/scope_cleanup_microservice.rb +116 -1
  27. data/lib/openc3/microservices/text_log_microservice.rb +4 -1
  28. data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +2 -0
  29. data/lib/openc3/models/activity_model.rb +15 -3
  30. data/lib/openc3/models/cvt_model.rb +2 -247
  31. data/lib/openc3/models/plugin_model.rb +9 -1
  32. data/lib/openc3/models/plugin_store_model.rb +1 -1
  33. data/lib/openc3/models/python_package_model.rb +1 -1
  34. data/lib/openc3/models/reaction_model.rb +27 -9
  35. data/lib/openc3/models/script_engine_model.rb +1 -1
  36. data/lib/openc3/models/target_model.rb +32 -34
  37. data/lib/openc3/models/tool_model.rb +18 -5
  38. data/lib/openc3/models/trigger_model.rb +25 -8
  39. data/lib/openc3/models/widget_model.rb +1 -2
  40. data/lib/openc3/operators/operator.rb +9 -7
  41. data/lib/openc3/packets/json_packet.rb +2 -0
  42. data/lib/openc3/packets/packet.rb +1 -0
  43. data/lib/openc3/packets/packet_config.rb +28 -12
  44. data/lib/openc3/script/api_shared.rb +39 -2
  45. data/lib/openc3/script/calendar.rb +40 -10
  46. data/lib/openc3/script/extract.rb +46 -13
  47. data/lib/openc3/script/script.rb +19 -0
  48. data/lib/openc3/script/storage.rb +6 -6
  49. data/lib/openc3/system/system.rb +6 -6
  50. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +0 -2
  51. data/lib/openc3/top_level.rb +15 -63
  52. data/lib/openc3/topics/decom_interface_topic.rb +19 -4
  53. data/lib/openc3/topics/interface_topic.rb +21 -2
  54. data/lib/openc3/topics/limits_event_topic.rb +1 -1
  55. data/lib/openc3/utilities/bucket_utilities.rb +3 -1
  56. data/lib/openc3/utilities/cli_generator.rb +7 -0
  57. data/lib/openc3/utilities/cmd_log.rb +1 -1
  58. data/lib/openc3/utilities/ctrf.rb +231 -0
  59. data/lib/openc3/utilities/local_mode.rb +3 -0
  60. data/lib/openc3/utilities/process_manager.rb +1 -1
  61. data/lib/openc3/utilities/python_proxy.rb +11 -4
  62. data/lib/openc3/utilities/questdb_client.rb +739 -22
  63. data/lib/openc3/utilities/running_script.rb +25 -7
  64. data/lib/openc3/utilities/script.rb +452 -0
  65. data/lib/openc3/utilities/secrets.rb +1 -1
  66. data/lib/openc3/version.rb +6 -6
  67. data/templates/conversion/conversion.py +0 -8
  68. data/templates/conversion/conversion.rb +0 -11
  69. data/templates/tool_angular/package.json +2 -2
  70. data/templates/tool_react/package.json +1 -1
  71. data/templates/tool_svelte/package.json +1 -1
  72. data/templates/tool_vue/package.json +3 -4
  73. data/templates/widget/package.json +2 -2
  74. metadata +17 -2
  75. data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +0 -23
@@ -0,0 +1,231 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2026 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is distributed in the hope that it will be useful,
7
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ # See LICENSE.md for more details.
10
+ #
11
+ # This file may also be used under the terms of a commercial license
12
+ # if purchased from OpenC3, Inc.
13
+
14
+ require 'openc3/version'
15
+ require 'date'
16
+
17
+ module OpenC3
18
+ # Utility class for converting COSMOS script reports to CTRF (Common Test Report Format)
19
+ # See https://ctrf.io/docs/category/specification
20
+ class Ctrf
21
+ # Convert a COSMOS plain text script report to CTRF JSON format
22
+ # @param report_content [String] Plain text script report
23
+ # @param version [String] Version string to include in CTRF output (defaults to OpenC3::VERSION)
24
+ # @return [Hash] CTRF formatted report as a Ruby hash
25
+ def self.convert_report(report_content, version: OpenC3::VERSION)
26
+ lines = report_content.split("\n")
27
+ tests = []
28
+ summary = {}
29
+ settings = {}
30
+ in_settings = false
31
+ last_result = nil
32
+ in_summary = false
33
+
34
+ lines.each do |line|
35
+ next if line.nil?
36
+ line_clean = line.strip
37
+
38
+ if line_clean == 'Settings:'
39
+ in_settings = true
40
+ next
41
+ end
42
+
43
+ if in_settings
44
+ if line_clean.include?('Manual')
45
+ parts = line.split('=')
46
+ settings[:manual] = parts[1].strip if parts[1]
47
+ next
48
+ elsif line_clean.include?('Pause on Error')
49
+ parts = line.split('=')
50
+ settings[:pauseOnError] = parts[1].strip if parts[1]
51
+ next
52
+ elsif line_clean.include?('Continue After Error')
53
+ parts = line.split('=')
54
+ settings[:continueAfterError] = parts[1].strip if parts[1]
55
+ next
56
+ elsif line_clean.include?('Abort After Error')
57
+ parts = line.split('=')
58
+ settings[:abortAfterError] = parts[1].strip if parts[1]
59
+ next
60
+ elsif line_clean.include?('Loop =')
61
+ parts = line.split('=')
62
+ settings[:loop] = parts[1].strip if parts[1]
63
+ next
64
+ elsif line_clean.include?('Break Loop On Error')
65
+ parts = line.split('=')
66
+ settings[:breakLoopOnError] = parts[1].strip if parts[1]
67
+ in_settings = false
68
+ next
69
+ end
70
+ end
71
+
72
+ if line_clean == 'Results:'
73
+ last_result = line_clean
74
+ next
75
+ end
76
+
77
+ if last_result
78
+ # The first line should always have a timestamp and what it is executing
79
+ # Format: "2026-04-02T19:45:41.228209Z: Executing MySuite:ExampleGroup:script_2"
80
+ if last_result == 'Results:' and line_clean.include?("Executing")
81
+ # Split on first ': ' to separate timestamp from message
82
+ timestamp_and_msg = line_clean.split(': ', 2)
83
+ if timestamp_and_msg.length >= 2
84
+ timestamp = timestamp_and_msg[0]
85
+ begin
86
+ summary[:startTime] = DateTime.parse(timestamp).to_time.to_f * 1000
87
+ rescue Date::Error
88
+ # Skip malformed timestamps
89
+ end
90
+ end
91
+ last_result = line_clean
92
+ next
93
+ end
94
+
95
+ # Format: "2026-04-02T19:45:44.041472Z: ExampleGroup:script_2:PASS"
96
+ # Check if line contains a test result (but not Executing or Completed)
97
+ if !line_clean.include?("Executing") && !line_clean.include?("Completed")
98
+ # Try to parse as a test result line
99
+ timestamp_and_msg = line_clean.split(': ', 2)
100
+ if timestamp_and_msg.length >= 2
101
+ result_string = timestamp_and_msg[1]
102
+ # Must have at least 2 colons for group:name:status format
103
+ result_parts = result_string.split(':')
104
+ if result_parts.length >= 3
105
+ # Get start time from last_result - could be Executing line or previous test result
106
+ start_time = nil
107
+ if last_result
108
+ last_timestamp_and_msg = last_result.split(': ', 2)
109
+ if last_timestamp_and_msg.length >= 2
110
+ begin
111
+ start_time = DateTime.parse(last_timestamp_and_msg[0]).to_time.to_f * 1000
112
+ rescue Date::Error
113
+ # Skip malformed timestamps
114
+ end
115
+ end
116
+ end
117
+
118
+ # Parse current line timestamp
119
+ timestamp = timestamp_and_msg[0]
120
+ begin
121
+ end_time = DateTime.parse(timestamp).to_time.to_f * 1000
122
+ rescue Date::Error
123
+ last_result = line_clean
124
+ next # Skip lines with malformed timestamps
125
+ end
126
+
127
+ # Parse the test result: ExampleGroup:script_2:PASS
128
+ suite_group = result_parts[0]
129
+ name = result_parts[1]
130
+ status = result_parts[2]
131
+
132
+ format_status = case status
133
+ when 'PASS'
134
+ 'passed'
135
+ when 'SKIP'
136
+ 'skipped'
137
+ when 'FAIL'
138
+ 'failed'
139
+ else
140
+ 'unknown'
141
+ end
142
+
143
+ tests << {
144
+ name: "#{suite_group}:#{name}",
145
+ status: format_status,
146
+ duration: start_time ? (end_time - start_time) : 0,
147
+ }
148
+ last_result = line_clean
149
+ next
150
+ end
151
+ end
152
+ end
153
+
154
+ # Format: "2026-04-02T19:45:44.044982Z: Completed MySuite:ExampleGroup:script_2"
155
+ if line_clean.include?("Completed")
156
+ timestamp_and_msg = line_clean.split(': ', 2)
157
+ if timestamp_and_msg.length >= 2
158
+ timestamp = timestamp_and_msg[0]
159
+ begin
160
+ summary[:stopTime] = DateTime.parse(timestamp).to_time.to_f * 1000
161
+ rescue Date::Error
162
+ # Skip malformed timestamps
163
+ end
164
+ end
165
+ last_result = nil
166
+ next
167
+ end
168
+ end
169
+
170
+ if line_clean == '--- Test Summary ---'
171
+ in_summary = true
172
+ next
173
+ end
174
+
175
+ if in_summary
176
+ if line_clean.include?("Total Tests")
177
+ parts = line_clean.split(':')
178
+ summary[:total] = parts[1].to_i if parts[1]
179
+ end
180
+ if line_clean.include?("Pass:")
181
+ parts = line_clean.split(':')
182
+ summary[:passed] = parts[1].to_i if parts[1]
183
+ end
184
+ if line_clean.include?("Skip:")
185
+ parts = line_clean.split(':')
186
+ summary[:skipped] = parts[1].to_i if parts[1]
187
+ end
188
+ if line_clean.include?("Fail:")
189
+ parts = line_clean.split(':')
190
+ summary[:failed] = parts[1].to_i if parts[1]
191
+ end
192
+ end
193
+ end
194
+
195
+ # Build CTRF report
196
+ # See https://ctrf.io/docs/specification/root
197
+ return {
198
+ reportFormat: "CTRF",
199
+ results: {
200
+ # See https://ctrf.io/docs/specification/tool
201
+ tool: {
202
+ name: "COSMOS Script Runner",
203
+ version: version,
204
+ },
205
+ # See https://ctrf.io/docs/specification/summary
206
+ summary: {
207
+ tests: summary[:total],
208
+ passed: summary[:passed],
209
+ failed: summary[:failed],
210
+ pending: 0,
211
+ skipped: summary[:skipped],
212
+ other: 0,
213
+ start: summary[:startTime],
214
+ stop: summary[:stopTime],
215
+ },
216
+ # See https://ctrf.io/docs/specification/tests
217
+ tests: tests,
218
+ # See https://ctrf.io/docs/specification/extra
219
+ extra: {
220
+ manual: settings[:manual],
221
+ pauseOnError: settings[:pauseOnError],
222
+ continueAfterError: settings[:continueAfterError],
223
+ abortAfterError: settings[:abortAfterError],
224
+ loop: settings[:loop],
225
+ breakLoopOnError: settings[:breakLoopOnError],
226
+ },
227
+ },
228
+ }
229
+ end
230
+ end
231
+ end
@@ -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