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.
- checksums.yaml +4 -4
- data/bin/openc3cli +58 -10
- data/bin/pipinstall +38 -6
- data/data/config/command_modifiers.yaml +1 -0
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/item_modifiers.yaml +20 -7
- data/data/config/table_parameter_modifiers.yaml +3 -1
- data/data/config/telemetry.yaml +1 -1
- data/lib/openc3/accessors/json_accessor.rb +1 -1
- data/lib/openc3/accessors/template_accessor.rb +9 -0
- data/lib/openc3/api/tlm_api.rb +3 -3
- data/lib/openc3/config/config_parser.rb +4 -4
- data/lib/openc3/conversions/conversion.rb +3 -3
- data/lib/openc3/core_ext/faraday.rb +4 -0
- data/lib/openc3/interfaces/interface.rb +1 -6
- data/lib/openc3/logs/log_writer.rb +24 -6
- data/lib/openc3/logs/packet_log_writer.rb +1 -4
- data/lib/openc3/logs/stream_log_pair.rb +11 -4
- data/lib/openc3/logs/text_log_writer.rb +1 -4
- data/lib/openc3/microservices/decom_microservice.rb +1 -1
- data/lib/openc3/microservices/interface_decom_common.rb +22 -8
- data/lib/openc3/microservices/interface_microservice.rb +14 -3
- data/lib/openc3/microservices/log_microservice.rb +7 -2
- data/lib/openc3/microservices/microservice.rb +10 -4
- data/lib/openc3/microservices/queue_microservice.rb +3 -0
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +116 -1
- data/lib/openc3/microservices/text_log_microservice.rb +4 -1
- data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +2 -0
- data/lib/openc3/models/activity_model.rb +15 -3
- data/lib/openc3/models/cvt_model.rb +2 -247
- data/lib/openc3/models/plugin_model.rb +9 -1
- data/lib/openc3/models/plugin_store_model.rb +1 -1
- data/lib/openc3/models/python_package_model.rb +1 -1
- data/lib/openc3/models/reaction_model.rb +27 -9
- data/lib/openc3/models/script_engine_model.rb +1 -1
- data/lib/openc3/models/target_model.rb +32 -34
- data/lib/openc3/models/tool_model.rb +18 -5
- data/lib/openc3/models/trigger_model.rb +25 -8
- data/lib/openc3/models/widget_model.rb +1 -2
- data/lib/openc3/operators/operator.rb +9 -7
- data/lib/openc3/packets/json_packet.rb +2 -0
- data/lib/openc3/packets/packet.rb +1 -0
- data/lib/openc3/packets/packet_config.rb +28 -12
- data/lib/openc3/script/api_shared.rb +39 -2
- data/lib/openc3/script/calendar.rb +40 -10
- data/lib/openc3/script/extract.rb +46 -13
- data/lib/openc3/script/script.rb +19 -0
- data/lib/openc3/script/storage.rb +6 -6
- data/lib/openc3/system/system.rb +6 -6
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +0 -2
- data/lib/openc3/top_level.rb +15 -63
- data/lib/openc3/topics/decom_interface_topic.rb +19 -4
- data/lib/openc3/topics/interface_topic.rb +21 -2
- data/lib/openc3/topics/limits_event_topic.rb +1 -1
- data/lib/openc3/utilities/bucket_utilities.rb +3 -1
- data/lib/openc3/utilities/cli_generator.rb +7 -0
- data/lib/openc3/utilities/cmd_log.rb +1 -1
- data/lib/openc3/utilities/ctrf.rb +231 -0
- data/lib/openc3/utilities/local_mode.rb +3 -0
- data/lib/openc3/utilities/process_manager.rb +1 -1
- data/lib/openc3/utilities/python_proxy.rb +11 -4
- data/lib/openc3/utilities/questdb_client.rb +739 -22
- data/lib/openc3/utilities/running_script.rb +25 -7
- data/lib/openc3/utilities/script.rb +452 -0
- data/lib/openc3/utilities/secrets.rb +1 -1
- data/lib/openc3/version.rb +6 -6
- data/templates/conversion/conversion.py +0 -8
- data/templates/conversion/conversion.rb +0 -11
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -4
- data/templates/widget/package.json +2 -2
- metadata +17 -2
- 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))
|
|
@@ -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
|
-
|
|
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
|