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
|
@@ -32,10 +32,12 @@ module OpenC3
|
|
|
32
32
|
ACTION_TYPES = [SCRIPT_REACTION, COMMAND_REACTION, NOTIFY_REACTION]
|
|
33
33
|
|
|
34
34
|
def self.create_unique_name(scope:)
|
|
35
|
-
reaction_names = self.names(scope: scope)
|
|
35
|
+
reaction_names = self.names(scope: scope)
|
|
36
36
|
num = 1 # Users count with 1
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
unless reaction_names.empty?
|
|
38
|
+
# Extract numeric suffixes and find the max to avoid lexicographic sort issues
|
|
39
|
+
max_num = reaction_names.map { |name| name[5..-1].to_i }.max
|
|
40
|
+
num = max_num + 1
|
|
39
41
|
end
|
|
40
42
|
return "REACT#{num}"
|
|
41
43
|
end
|
|
@@ -77,7 +79,7 @@ module OpenC3
|
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
attr_reader :name, :scope, :snooze, :triggers, :actions, :enabled, :trigger_level, :snoozed_until
|
|
80
|
-
attr_accessor :username, :shard
|
|
82
|
+
attr_accessor :username, :shard, :label
|
|
81
83
|
|
|
82
84
|
def initialize(
|
|
83
85
|
name:,
|
|
@@ -90,6 +92,7 @@ module OpenC3
|
|
|
90
92
|
snoozed_until: nil,
|
|
91
93
|
username: nil,
|
|
92
94
|
shard: 0,
|
|
95
|
+
label: nil,
|
|
93
96
|
updated_at: nil
|
|
94
97
|
)
|
|
95
98
|
super("#{scope}#{PRIMARY_KEY}", name: name, scope: scope)
|
|
@@ -102,6 +105,7 @@ module OpenC3
|
|
|
102
105
|
@triggers = validate_triggers(triggers)
|
|
103
106
|
@username = username
|
|
104
107
|
@shard = shard.to_i # to_i to handle nil
|
|
108
|
+
@label = label
|
|
105
109
|
@updated_at = updated_at
|
|
106
110
|
end
|
|
107
111
|
|
|
@@ -177,28 +181,40 @@ module OpenC3
|
|
|
177
181
|
return actions
|
|
178
182
|
end
|
|
179
183
|
|
|
180
|
-
|
|
184
|
+
# Validate that all triggers exist, but do not persist dependent changes yet.
|
|
185
|
+
# Returns the list of trigger models that need updating.
|
|
186
|
+
def validate_triggers_exist
|
|
181
187
|
if @triggers.empty?
|
|
182
188
|
raise ReactionInputError.new "reaction must contain at least one valid trigger: #{@triggers}"
|
|
183
189
|
end
|
|
184
190
|
|
|
191
|
+
models_to_update = []
|
|
185
192
|
@triggers.each do | trigger |
|
|
186
193
|
model = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: @scope)
|
|
187
194
|
if model.nil?
|
|
188
195
|
raise ReactionInputError.new "failed to find trigger: #{trigger}"
|
|
189
196
|
end
|
|
190
|
-
model.
|
|
191
|
-
|
|
197
|
+
unless model.dependents.include?(@name)
|
|
198
|
+
model.update_dependents(dependent: @name)
|
|
199
|
+
models_to_update << model
|
|
200
|
+
end
|
|
192
201
|
end
|
|
202
|
+
models_to_update
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Persist dependent changes to trigger models
|
|
206
|
+
def commit_trigger_dependents(models)
|
|
207
|
+
models.each { |model| model.update() }
|
|
193
208
|
end
|
|
194
209
|
|
|
195
210
|
def create
|
|
196
211
|
unless Store.hget(@primary_key, @name).nil?
|
|
197
212
|
raise ReactionInputError.new "existing reaction found: #{@name}"
|
|
198
213
|
end
|
|
199
|
-
|
|
214
|
+
models = validate_triggers_exist()
|
|
200
215
|
@updated_at = Time.now.to_nsec_from_epoch
|
|
201
216
|
Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true))
|
|
217
|
+
commit_trigger_dependents(models)
|
|
202
218
|
notify(kind: 'created')
|
|
203
219
|
end
|
|
204
220
|
|
|
@@ -222,9 +238,10 @@ module OpenC3
|
|
|
222
238
|
end
|
|
223
239
|
end
|
|
224
240
|
|
|
225
|
-
|
|
241
|
+
models = validate_triggers_exist()
|
|
226
242
|
@updated_at = Time.now.to_nsec_from_epoch
|
|
227
243
|
Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true))
|
|
244
|
+
commit_trigger_dependents(models)
|
|
228
245
|
# No notification as this is only called via reaction_controller which already notifies
|
|
229
246
|
end
|
|
230
247
|
|
|
@@ -281,6 +298,7 @@ module OpenC3
|
|
|
281
298
|
'actions' => @actions,
|
|
282
299
|
'username' => @username,
|
|
283
300
|
'shard' => @shard,
|
|
301
|
+
'label' => @label,
|
|
284
302
|
'updated_at' => @updated_at
|
|
285
303
|
}
|
|
286
304
|
end
|
|
@@ -164,36 +164,41 @@ module OpenC3
|
|
|
164
164
|
if target_name.include?('..') || target_name.include?('/') || target_name.include?('\\')
|
|
165
165
|
raise ArgumentError, "Invalid target_name: #{target_name.inspect}"
|
|
166
166
|
end
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
temp_dir = Dir.mktmpdir
|
|
168
|
+
begin
|
|
169
|
+
zip_filename = OpenC3.sanitize_path(File.join(temp_dir, "#{target_name}.zip"))
|
|
170
|
+
Zip.continue_on_exists_proc = true
|
|
171
|
+
zip = Zip::File.open(zip_filename, create: true)
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
if ENV['OPENC3_LOCAL_MODE']
|
|
174
|
+
OpenC3::LocalMode.zip_target(target_name, zip, scope: scope)
|
|
175
|
+
else
|
|
176
|
+
bucket = Bucket.getClient()
|
|
177
|
+
# The trailing slash is important!
|
|
178
|
+
prefix = "#{scope}/targets_modified/#{target_name}/"
|
|
179
|
+
resp = bucket.list_objects(
|
|
180
|
+
bucket: ENV['OPENC3_CONFIG_BUCKET'],
|
|
181
|
+
prefix: prefix,
|
|
182
|
+
)
|
|
183
|
+
resp.each do |item|
|
|
184
|
+
# item.key looks like DEFAULT/targets_modified/INST/screens/blah.txt
|
|
185
|
+
base_path = item.key.sub(prefix, '') # remove prefix
|
|
186
|
+
local_path = File.join(temp_dir, base_path)
|
|
187
|
+
# Ensure dir structure exists, get_object fails if not
|
|
188
|
+
FileUtils.mkdir_p(File.dirname(local_path))
|
|
189
|
+
bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: item.key, path: local_path)
|
|
190
|
+
zip.add(base_path, local_path)
|
|
191
|
+
end
|
|
190
192
|
end
|
|
193
|
+
zip.close
|
|
194
|
+
|
|
195
|
+
result = OpenStruct.new
|
|
196
|
+
result.filename = File.basename(zip_filename)
|
|
197
|
+
result.contents = File.read(zip_filename, mode: 'rb')
|
|
198
|
+
ensure
|
|
199
|
+
FileUtils.remove_entry_secure(temp_dir, true)
|
|
191
200
|
end
|
|
192
|
-
zip.close
|
|
193
201
|
|
|
194
|
-
result = OpenStruct.new
|
|
195
|
-
result.filename = File.basename(zip_filename)
|
|
196
|
-
result.contents = File.read(zip_filename, mode: 'rb')
|
|
197
202
|
return result
|
|
198
203
|
end
|
|
199
204
|
|
|
@@ -393,14 +398,7 @@ module OpenC3
|
|
|
393
398
|
shard: 0,
|
|
394
399
|
scope:
|
|
395
400
|
)
|
|
396
|
-
super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at,
|
|
397
|
-
cmd_buffer_depth: cmd_buffer_depth, cmd_log_cycle_time: cmd_log_cycle_time, cmd_log_cycle_size: cmd_log_cycle_size,
|
|
398
|
-
cmd_log_retain_time: cmd_log_retain_time,
|
|
399
|
-
tlm_buffer_depth: tlm_buffer_depth, tlm_log_cycle_time: tlm_log_cycle_time, tlm_log_cycle_size: tlm_log_cycle_size,
|
|
400
|
-
tlm_log_retain_time: tlm_log_retain_time,
|
|
401
|
-
cmd_decom_retain_time: cmd_decom_retain_time, tlm_decom_retain_time: tlm_decom_retain_time,
|
|
402
|
-
cleanup_poll_time: cleanup_poll_time, needs_dependencies: needs_dependencies, target_microservices: target_microservices,
|
|
403
|
-
scope: scope)
|
|
401
|
+
super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at, scope: scope)
|
|
404
402
|
@folder_name = folder_name
|
|
405
403
|
@requires = requires
|
|
406
404
|
@ignored_parameters = ignored_parameters
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
18
|
require 'openc3/models/model'
|
|
19
|
-
require 'openc3/models/scope_model'
|
|
19
|
+
# require 'openc3/models/scope_model' # Circular require
|
|
20
20
|
require 'openc3/utilities/bucket'
|
|
21
21
|
require 'openc3/utilities/bucket_utilities'
|
|
22
22
|
require 'rack'
|
|
@@ -179,7 +179,7 @@ module OpenC3
|
|
|
179
179
|
end
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
-
if @url and !@url.start_with?('/') and @url !~ URI::
|
|
182
|
+
if @url and !@url.start_with?('/') and @url !~ URI::RFC2396_PARSER.make_regexp
|
|
183
183
|
raise "URL must be a full URL (http://domain.com/path) or a relative path (/path)"
|
|
184
184
|
end
|
|
185
185
|
|
|
@@ -250,9 +250,22 @@ module OpenC3
|
|
|
250
250
|
|
|
251
251
|
variables["tool_name"] = @name
|
|
252
252
|
start_path = "/tools/#{@folder_name}/"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
# Sort files so dependencies are uploaded before dependents:
|
|
254
|
+
# fonts first, then CSS, then index.html last (it triggers all other loads)
|
|
255
|
+
filenames = Dir.glob(gem_path + start_path + "**/*")
|
|
256
|
+
filenames.reject! { |f| f == '.' or f == '..' or File.directory?(f) }
|
|
257
|
+
filenames.sort_by! do |filename|
|
|
258
|
+
if filename.include?('/fonts/')
|
|
259
|
+
[0, filename]
|
|
260
|
+
elsif filename.include?('/css/')
|
|
261
|
+
[1, filename]
|
|
262
|
+
elsif File.basename(filename) == 'index.html'
|
|
263
|
+
[3, filename]
|
|
264
|
+
else
|
|
265
|
+
[2, filename]
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
filenames.each do |filename|
|
|
256
269
|
key = filename.split(gem_path + '/tools/')[-1]
|
|
257
270
|
extension = filename.split('.')[-1]
|
|
258
271
|
content_type = Rack::Mime.mime_type(".#{extension}")
|
|
@@ -19,7 +19,7 @@ require 'openc3/models/model'
|
|
|
19
19
|
require 'openc3/models/microservice_model'
|
|
20
20
|
require 'openc3/models/target_model'
|
|
21
21
|
require 'openc3/models/trigger_group_model'
|
|
22
|
-
require 'openc3/models/reaction_model'
|
|
22
|
+
# require 'openc3/models/reaction_model' # Remove circular require
|
|
23
23
|
require 'openc3/topics/autonomic_topic'
|
|
24
24
|
|
|
25
25
|
module OpenC3
|
|
@@ -52,10 +52,12 @@ module OpenC3
|
|
|
52
52
|
TRIGGER_TYPE = 'trigger'.freeze
|
|
53
53
|
|
|
54
54
|
def self.create_unique_name(group:, scope:)
|
|
55
|
-
trigger_names = self.names(group: group, scope: scope)
|
|
55
|
+
trigger_names = self.names(group: group, scope: scope)
|
|
56
56
|
num = 1 # Users count with 1
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
unless trigger_names.empty?
|
|
58
|
+
# Extract numeric suffixes and find the max to avoid lexicographic sort issues
|
|
59
|
+
max_num = trigger_names.map { |name| name[4..-1].to_i }.max
|
|
60
|
+
num = max_num + 1
|
|
59
61
|
end
|
|
60
62
|
return "TRIG#{num}"
|
|
61
63
|
end
|
|
@@ -97,6 +99,7 @@ module OpenC3
|
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
attr_reader :name, :scope, :state, :group, :enabled, :left, :operator, :right, :dependents, :roots
|
|
102
|
+
attr_accessor :label
|
|
100
103
|
|
|
101
104
|
def initialize(
|
|
102
105
|
name:,
|
|
@@ -108,6 +111,7 @@ module OpenC3
|
|
|
108
111
|
state: false,
|
|
109
112
|
enabled: true,
|
|
110
113
|
dependents: nil,
|
|
114
|
+
label: nil,
|
|
111
115
|
updated_at: nil
|
|
112
116
|
)
|
|
113
117
|
super("#{scope}#{PRIMARY_KEY}#{group}", name: name, scope: scope)
|
|
@@ -119,6 +123,7 @@ module OpenC3
|
|
|
119
123
|
@operator = validate_operator(operator: operator)
|
|
120
124
|
@right = validate_operand(operand: right, right: true)
|
|
121
125
|
@dependents = dependents
|
|
126
|
+
@label = label
|
|
122
127
|
@updated_at = updated_at
|
|
123
128
|
selected_group = TriggerGroupModel.get(name: @group, scope: @scope)
|
|
124
129
|
if selected_group.nil?
|
|
@@ -175,8 +180,11 @@ module OpenC3
|
|
|
175
180
|
end
|
|
176
181
|
end
|
|
177
182
|
|
|
178
|
-
|
|
183
|
+
# Validate that all root triggers exist, but do not persist dependent changes yet.
|
|
184
|
+
# Returns the list of root trigger models that need updating.
|
|
185
|
+
def validate_roots
|
|
179
186
|
@dependents = [] if @dependents.nil?
|
|
187
|
+
models_to_update = []
|
|
180
188
|
@roots.each do | trigger |
|
|
181
189
|
model = TriggerModel.get(name: trigger, group: @group, scope: @scope)
|
|
182
190
|
if model.nil?
|
|
@@ -184,25 +192,33 @@ module OpenC3
|
|
|
184
192
|
end
|
|
185
193
|
unless model.dependents.include?(@name)
|
|
186
194
|
model.update_dependents(dependent: @name)
|
|
187
|
-
model
|
|
195
|
+
models_to_update << model
|
|
188
196
|
end
|
|
189
197
|
end
|
|
198
|
+
models_to_update
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Persist dependent changes to root triggers
|
|
202
|
+
def commit_roots(models)
|
|
203
|
+
models.each { |model| model.update() }
|
|
190
204
|
end
|
|
191
205
|
|
|
192
206
|
def create
|
|
193
207
|
unless Store.hget(@primary_key, @name).nil?
|
|
194
208
|
raise TriggerInputError.new "existing trigger found: '#{@name}'"
|
|
195
209
|
end
|
|
196
|
-
|
|
210
|
+
models = validate_roots()
|
|
197
211
|
@updated_at = Time.now.to_nsec_from_epoch
|
|
198
212
|
Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true))
|
|
213
|
+
commit_roots(models)
|
|
199
214
|
notify(kind: 'created')
|
|
200
215
|
end
|
|
201
216
|
|
|
202
217
|
def update
|
|
203
|
-
|
|
218
|
+
models = validate_roots()
|
|
204
219
|
@updated_at = Time.now.to_nsec_from_epoch
|
|
205
220
|
Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true))
|
|
221
|
+
commit_roots(models)
|
|
206
222
|
# No notification as this is only called via trigger_controller which already notifies
|
|
207
223
|
end
|
|
208
224
|
|
|
@@ -267,6 +283,7 @@ module OpenC3
|
|
|
267
283
|
'left' => @left,
|
|
268
284
|
'operator' => @operator,
|
|
269
285
|
'right' => @right,
|
|
286
|
+
'label' => @label,
|
|
270
287
|
'updated_at' => @updated_at,
|
|
271
288
|
}
|
|
272
289
|
end
|
|
@@ -15,9 +15,8 @@
|
|
|
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 'openc3/top_level'
|
|
19
18
|
require 'openc3/models/model'
|
|
20
|
-
require 'openc3/models/scope_model'
|
|
19
|
+
# require 'openc3/models/scope_model' # Circular require
|
|
21
20
|
require 'openc3/utilities/bucket'
|
|
22
21
|
require 'openc3/utilities/bucket_utilities'
|
|
23
22
|
|
|
@@ -54,8 +54,10 @@ module OpenC3
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def finalize
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
unless closed?
|
|
58
|
+
extract()
|
|
59
|
+
close()
|
|
60
|
+
end
|
|
59
61
|
unlink()
|
|
60
62
|
|
|
61
63
|
output = ''
|
|
@@ -174,7 +176,7 @@ module OpenC3
|
|
|
174
176
|
end
|
|
175
177
|
@process.stop
|
|
176
178
|
end
|
|
177
|
-
FileUtils.remove_entry_secure(@temp_dir, true)
|
|
179
|
+
FileUtils.remove_entry_secure(@temp_dir, true) if @temp_dir
|
|
178
180
|
@process = nil
|
|
179
181
|
end
|
|
180
182
|
|
|
@@ -300,6 +302,7 @@ module OpenC3
|
|
|
300
302
|
# Respawn process
|
|
301
303
|
output = p.extract_output
|
|
302
304
|
Logger.error("Unexpected process died... respawning! #{p.cmd_line}\n#{output}\n", scope: p.scope)
|
|
305
|
+
p.hard_stop
|
|
303
306
|
p.start
|
|
304
307
|
end
|
|
305
308
|
end
|
|
@@ -308,6 +311,7 @@ module OpenC3
|
|
|
308
311
|
|
|
309
312
|
def shutdown_processes(processes)
|
|
310
313
|
# Make a copy so we don't mutate original
|
|
314
|
+
hard_stop_processes = processes.dup
|
|
311
315
|
processes = processes.dup
|
|
312
316
|
|
|
313
317
|
Logger.info("Commanding soft stops...")
|
|
@@ -331,10 +335,8 @@ module OpenC3
|
|
|
331
335
|
end
|
|
332
336
|
sleep(0.1)
|
|
333
337
|
end
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
processes.each { |_name, p| p.hard_stop }
|
|
337
|
-
end
|
|
338
|
+
Logger.debug("Commanding hard stops...")
|
|
339
|
+
hard_stop_processes.each { |_name, p| p.output_increment; p.extract_output; p.hard_stop }
|
|
338
340
|
end
|
|
339
341
|
|
|
340
342
|
def shutdown
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
18
|
require 'digest'
|
|
19
|
+
require 'active_support/core_ext/object/deep_dup'
|
|
19
20
|
require 'openc3/packets/structure'
|
|
20
21
|
require 'openc3/packets/packet_item'
|
|
21
22
|
require 'openc3/ext/packet' if RUBY_ENGINE == 'ruby' and !ENV['OPENC3_NO_EXT']
|
|
@@ -252,7 +252,7 @@ module OpenC3
|
|
|
252
252
|
#######################################################################
|
|
253
253
|
when 'STATE', 'READ_CONVERSION', 'WRITE_CONVERSION', 'POLY_READ_CONVERSION',\
|
|
254
254
|
'POLY_WRITE_CONVERSION', 'SEG_POLY_READ_CONVERSION', 'SEG_POLY_WRITE_CONVERSION',\
|
|
255
|
-
'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START', 'REQUIRED',\
|
|
255
|
+
'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START', 'CONVERTED_DATA', 'REQUIRED',\
|
|
256
256
|
'LIMITS', 'LIMITS_RESPONSE', 'UNITS', 'FORMAT_STRING', 'DESCRIPTION',\
|
|
257
257
|
'MINIMUM_VALUE', 'MAXIMUM_VALUE', 'DEFAULT_VALUE', 'OVERFLOW', 'OVERLAP', 'KEY', 'VARIABLE_BIT_SIZE',\
|
|
258
258
|
'OBFUSCATE'
|
|
@@ -675,11 +675,6 @@ module OpenC3
|
|
|
675
675
|
klass = OpenC3.require_class(params[0])
|
|
676
676
|
conversion = klass.new(*params[1..(params.length - 1)])
|
|
677
677
|
@current_item.public_send("#{keyword.downcase}=".to_sym, conversion)
|
|
678
|
-
if klass != ProcessorConversion and (conversion.converted_type.nil? or conversion.converted_bit_size.nil?)
|
|
679
|
-
msg = "Read Conversion #{params[0]} on item #{@current_item.name} does not specify converted type or bit size"
|
|
680
|
-
@warnings << msg
|
|
681
|
-
Logger.instance.warn @warnings[-1]
|
|
682
|
-
end
|
|
683
678
|
else
|
|
684
679
|
conversion = PythonProxy.new('Conversion', params[0], *params[1..(params.length - 1)])
|
|
685
680
|
@current_item.public_send("#{keyword.downcase}=".to_sym, conversion)
|
|
@@ -719,8 +714,9 @@ module OpenC3
|
|
|
719
714
|
# All config.lines following this config.line are considered part
|
|
720
715
|
# of the conversion until an end of conversion marker is found
|
|
721
716
|
when 'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START'
|
|
722
|
-
|
|
723
|
-
|
|
717
|
+
# As of COSMOS 7 the converted type and bit size are deprecated
|
|
718
|
+
# but we're still allowing them to be defined as parameters for backward compatibility
|
|
719
|
+
parser.verify_num_parameters(0, 2, keyword)
|
|
724
720
|
@proc_text = ''
|
|
725
721
|
@building_generic_conversion = true
|
|
726
722
|
parser.set_preserve_lines(true)
|
|
@@ -731,10 +727,30 @@ module OpenC3
|
|
|
731
727
|
raise parser.error("Invalid converted_type: #{@converted_type}.") unless CONVERTED_DATA_TYPES.include? @converted_type
|
|
732
728
|
end
|
|
733
729
|
@converted_bit_size = Integer(params[1]) if params[1]
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
730
|
+
|
|
731
|
+
# Define the converted data type, bit size, and optional array size
|
|
732
|
+
# for items with read conversions (especially DERIVED items)
|
|
733
|
+
when 'CONVERTED_DATA'
|
|
734
|
+
usage = "CONVERTED_DATA <Converted Bit Size> <Converted Type> <Converted Array Size (optional)>"
|
|
735
|
+
parser.verify_num_parameters(2, 3, usage)
|
|
736
|
+
raise parser.error("#{keyword} requires a current item") unless @current_item
|
|
737
|
+
raise parser.error("#{keyword} requires a current item with a conversion") unless @current_item.read_conversion or @current_item.write_conversion
|
|
738
|
+
converted_bit_size = Integer(params[0])
|
|
739
|
+
converted_type = params[1].upcase.intern
|
|
740
|
+
raise parser.error("Invalid converted_type: #{converted_type}.") unless CONVERTED_DATA_TYPES.include? converted_type
|
|
741
|
+
if @current_item.read_conversion
|
|
742
|
+
@current_item.read_conversion.converted_type = converted_type
|
|
743
|
+
@current_item.read_conversion.converted_bit_size = converted_bit_size
|
|
744
|
+
if params[2]
|
|
745
|
+
@current_item.read_conversion.converted_array_size = Integer(params[2])
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
if @current_item.write_conversion
|
|
749
|
+
@current_item.write_conversion.converted_type = converted_type
|
|
750
|
+
@current_item.write_conversion.converted_bit_size = converted_bit_size
|
|
751
|
+
if params[2]
|
|
752
|
+
@current_item.write_conversion.converted_array_size = Integer(params[2])
|
|
753
|
+
end
|
|
738
754
|
end
|
|
739
755
|
|
|
740
756
|
# Define a set of limits for the current telemetry item
|
|
@@ -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
|
-
|
|
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
|
|
@@ -52,21 +52,37 @@ module OpenC3
|
|
|
52
52
|
return _cal_handle_response(response, 'Failed to delete timeline')
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Creates an activity for the specified timeline.
|
|
56
|
+
#
|
|
57
|
+
# @param name [String] The name of the timeline.
|
|
58
|
+
# @param kind [String] The kind of activity. Must be one of "COMMAND", "SCRIPT", or "RESERVE".
|
|
59
|
+
# @param start [DateTime] The start time of the activity.
|
|
60
|
+
# @param stop [DateTime] The stop time of the activity.
|
|
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
|
+
# @param scope [String, optional] The scope of the activity. Defaults to OPENC3_SCOPE, must correspond to the timeline.
|
|
55
63
|
def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
|
|
56
|
-
|
|
57
|
-
kinds = %w(command script reserve)
|
|
58
|
-
unless kinds.include?(kind)
|
|
59
|
-
raise "Unknown kind: #{kind}. Must be one of #{kinds.join(', ')}."
|
|
60
|
-
end
|
|
61
|
-
post_data = {}
|
|
62
|
-
post_data['start'] = start.to_datetime.iso8601
|
|
63
|
-
post_data['stop'] = stop.to_datetime.iso8601
|
|
64
|
-
post_data['kind'] = kind
|
|
65
|
-
post_data['data'] = data
|
|
64
|
+
post_data = _build_activity_data(kind, start, stop, data)
|
|
66
65
|
response = $api_server.request('post', "/openc3-api/timeline/#{name}/activities", data: post_data, json: true, scope: scope)
|
|
67
66
|
return _cal_handle_response(response, 'Failed to create timeline activity')
|
|
68
67
|
end
|
|
69
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
|
+
|
|
70
86
|
def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
|
|
71
87
|
response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
|
|
72
88
|
return _cal_handle_response(response, 'Failed to get timeline activity')
|
|
@@ -89,6 +105,20 @@ module OpenC3
|
|
|
89
105
|
return _cal_handle_response(response, 'Failed to delete timeline activity')
|
|
90
106
|
end
|
|
91
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
|
+
|
|
92
122
|
# Helper method to handle the response
|
|
93
123
|
def _cal_handle_response(response, error_message)
|
|
94
124
|
return nil if response.nil?
|