openc3 6.4.2 → 6.5.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.
- checksums.yaml +4 -4
- data/bin/openc3cli +172 -97
- data/data/config/_graph_params.yaml +4 -4
- data/data/config/conversions.yaml +2 -1
- data/data/config/item_modifiers.yaml +1 -0
- data/data/config/plugins.yaml +14 -1
- data/data/config/processors.yaml +51 -0
- data/data/config/telemetry_modifiers.yaml +1 -0
- data/lib/openc3/api/tlm_api.rb +10 -5
- data/lib/openc3/microservices/interface_microservice.rb +15 -9
- data/lib/openc3/models/plugin_model.rb +5 -4
- data/lib/openc3/models/scope_model.rb +87 -57
- data/lib/openc3/models/script_engine_model.rb +93 -0
- data/lib/openc3/models/script_status_model.rb +4 -0
- data/lib/openc3/models/target_model.rb +7 -1
- data/lib/openc3/script/script.rb +5 -1
- data/lib/openc3/script_engines/script_engine.rb +118 -0
- data/lib/openc3/topics/interface_topic.rb +23 -3
- data/lib/openc3/utilities/cli_generator.rb +42 -15
- data/lib/openc3/utilities/running_script.rb +1460 -0
- data/lib/openc3/version.rb +6 -6
- data/templates/conversion/conversion.py +1 -1
- data/templates/conversion/conversion.rb +1 -1
- data/templates/processor/processor.py +32 -0
- data/templates/processor/processor.rb +36 -0
- 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 -3
- data/templates/widget/package.json +2 -2
- metadata +7 -1
@@ -20,17 +20,17 @@
|
|
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
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require
|
23
|
+
require "openc3/version"
|
24
|
+
require "openc3/models/model"
|
25
|
+
require "openc3/models/plugin_model"
|
26
|
+
require "openc3/models/microservice_model"
|
27
|
+
require "openc3/models/setting_model"
|
28
|
+
require "openc3/models/trigger_group_model"
|
29
|
+
require "openc3/topics/system_events_topic"
|
30
30
|
|
31
31
|
begin
|
32
|
-
require
|
33
|
-
require
|
32
|
+
require "openc3-enterprise/models/cmd_authority_model"
|
33
|
+
require "openc3-enterprise/models/critical_cmd_model"
|
34
34
|
module OpenC3
|
35
35
|
class ScopeModel < Model
|
36
36
|
ENTERPRISE = true
|
@@ -46,7 +46,7 @@ end
|
|
46
46
|
|
47
47
|
module OpenC3
|
48
48
|
class ScopeModel < Model
|
49
|
-
PRIMARY_KEY =
|
49
|
+
PRIMARY_KEY = "openc3_scopes"
|
50
50
|
|
51
51
|
attr_accessor :children
|
52
52
|
attr_accessor :text_log_cycle_time
|
@@ -76,17 +76,17 @@ module OpenC3
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def self.from_json(json, scope: nil)
|
79
|
-
json = JSON.parse(json, :
|
79
|
+
json = JSON.parse(json, allow_nan: true, create_additions: true) if String === json
|
80
80
|
raise "json data is nil" if json.nil?
|
81
|
-
|
81
|
+
new(**json.transform_keys(&:to_sym))
|
82
82
|
end
|
83
83
|
|
84
84
|
def self.get_model(name:, scope: nil)
|
85
85
|
json = get(name: name)
|
86
86
|
if json
|
87
|
-
|
87
|
+
from_json(json)
|
88
88
|
else
|
89
|
-
|
89
|
+
nil
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -99,8 +99,7 @@ module OpenC3
|
|
99
99
|
command_authority: false,
|
100
100
|
critical_commanding: "OFF",
|
101
101
|
shard: 0,
|
102
|
-
updated_at: nil
|
103
|
-
)
|
102
|
+
updated_at: nil)
|
104
103
|
super(
|
105
104
|
PRIMARY_KEY,
|
106
105
|
name: name,
|
@@ -117,7 +116,7 @@ module OpenC3
|
|
117
116
|
@command_authority = command_authority
|
118
117
|
@critical_commanding = critical_commanding.to_s.upcase
|
119
118
|
@critical_commanding = "OFF" if @critical_commanding.length == 0
|
120
|
-
if
|
119
|
+
if !["OFF", "NORMAL", "ALL"].include?(@critical_commanding)
|
121
120
|
raise "Invalid value for critical_commanding: #{@critical_commanding}"
|
122
121
|
end
|
123
122
|
@shard = shard.to_i # to_i to handle nil
|
@@ -126,7 +125,7 @@ module OpenC3
|
|
126
125
|
|
127
126
|
def create(update: false, force: false, queued: false)
|
128
127
|
# Ensure there are no "." in the scope name - prevents gems accidentally becoming scope names
|
129
|
-
raise "Invalid scope name: #{@name}" if
|
128
|
+
raise "Invalid scope name: #{@name}" if !/^[a-zA-Z0-9_-]+$/.match?(@name)
|
130
129
|
@name = @name.upcase
|
131
130
|
@scope = @name # Ensure @scope matches @name
|
132
131
|
# Ensure the various cycle and retain times are integers
|
@@ -135,7 +134,7 @@ module OpenC3
|
|
135
134
|
@text_log_retain_time = @text_log_retain_time.to_i if @text_log_retain_time
|
136
135
|
@tool_log_retain_time = @tool_log_retain_time.to_i if @tool_log_retain_time
|
137
136
|
@cleanup_poll_time = @cleanup_poll_time.to_i
|
138
|
-
super
|
137
|
+
super
|
139
138
|
|
140
139
|
if ENTERPRISE
|
141
140
|
# If we're updating the scope and disabling command_authority
|
@@ -157,50 +156,49 @@ module OpenC3
|
|
157
156
|
end
|
158
157
|
end
|
159
158
|
|
160
|
-
SystemEventsTopic.write(:scope, as_json
|
159
|
+
SystemEventsTopic.write(:scope, as_json)
|
161
160
|
end
|
162
161
|
|
163
162
|
def destroy
|
164
|
-
if @name !=
|
163
|
+
if @name != "DEFAULT"
|
165
164
|
# Remove all the plugins for this scope
|
166
165
|
plugins = PluginModel.get_all_models(scope: @name)
|
167
166
|
plugins.each do |_plugin_name, plugin|
|
168
167
|
plugin.destroy
|
169
168
|
end
|
170
|
-
super
|
169
|
+
super
|
171
170
|
else
|
172
171
|
raise "DEFAULT scope cannot be destroyed"
|
173
172
|
end
|
174
173
|
end
|
175
174
|
|
176
175
|
def as_json(*_a)
|
177
|
-
{
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
}
|
176
|
+
{"name" => @name,
|
177
|
+
"updated_at" => @updated_at,
|
178
|
+
"text_log_cycle_time" => @text_log_cycle_time,
|
179
|
+
"text_log_cycle_size" => @text_log_cycle_size,
|
180
|
+
"text_log_retain_time" => @text_log_retain_time,
|
181
|
+
"tool_log_retain_time" => @tool_log_retain_time,
|
182
|
+
"cleanup_poll_time" => @cleanup_poll_time,
|
183
|
+
"command_authority" => @command_authority,
|
184
|
+
"critical_commanding" => @critical_commanding,
|
185
|
+
"shard" => @shard}
|
188
186
|
end
|
189
187
|
|
190
188
|
def deploy_openc3_log_messages_microservice(gem_path, variables, parent)
|
191
189
|
microservice_name = "#{@scope}__OPENC3__LOG"
|
192
190
|
topics = ["#{@scope}__openc3_log_messages"]
|
193
191
|
# Also log the NOSCOPE messages with this microservice for the DEFAULT scope
|
194
|
-
if @scope ==
|
192
|
+
if @scope == "DEFAULT"
|
195
193
|
topics << "NOSCOPE__openc3_log_messages"
|
196
194
|
end
|
197
195
|
microservice = MicroserviceModel.new(
|
198
196
|
name: microservice_name,
|
199
197
|
cmd: ["ruby", "text_log_microservice.rb", microservice_name],
|
200
|
-
work_dir:
|
198
|
+
work_dir: "/openc3/lib/openc3/microservices",
|
201
199
|
options: [
|
202
200
|
["CYCLE_TIME", @text_log_cycle_time],
|
203
|
-
["CYCLE_SIZE", @text_log_cycle_size]
|
201
|
+
["CYCLE_SIZE", @text_log_cycle_size]
|
204
202
|
],
|
205
203
|
topics: topics,
|
206
204
|
parent: parent,
|
@@ -218,11 +216,11 @@ module OpenC3
|
|
218
216
|
microservice = MicroserviceModel.new(
|
219
217
|
name: microservice_name,
|
220
218
|
cmd: ["ruby", "log_microservice.rb", microservice_name],
|
221
|
-
work_dir:
|
219
|
+
work_dir: "/openc3/lib/openc3/microservices",
|
222
220
|
options: [
|
223
221
|
["RAW_OR_DECOM", "RAW"],
|
224
222
|
["CMD_OR_TLM", "CMD"],
|
225
|
-
["CYCLE_TIME", "3600"]
|
223
|
+
["CYCLE_TIME", "3600"] # Keep at most 1 hour per log
|
226
224
|
],
|
227
225
|
topics: ["#{@scope}__COMMAND__{UNKNOWN}__UNKNOWN"],
|
228
226
|
target_names: [],
|
@@ -241,11 +239,11 @@ module OpenC3
|
|
241
239
|
microservice = MicroserviceModel.new(
|
242
240
|
name: microservice_name,
|
243
241
|
cmd: ["ruby", "log_microservice.rb", microservice_name],
|
244
|
-
work_dir:
|
242
|
+
work_dir: "/openc3/lib/openc3/microservices",
|
245
243
|
options: [
|
246
244
|
["RAW_OR_DECOM", "RAW"],
|
247
245
|
["CMD_OR_TLM", "TLM"],
|
248
|
-
["CYCLE_TIME", "3600"]
|
246
|
+
["CYCLE_TIME", "3600"] # Keep at most 1 hour per log
|
249
247
|
],
|
250
248
|
topics: ["#{@scope}__TELEMETRY__{UNKNOWN}__UNKNOWN"],
|
251
249
|
target_names: [],
|
@@ -264,7 +262,7 @@ module OpenC3
|
|
264
262
|
microservice = MicroserviceModel.new(
|
265
263
|
name: microservice_name,
|
266
264
|
cmd: ["ruby", "periodic_microservice.rb", microservice_name],
|
267
|
-
work_dir:
|
265
|
+
work_dir: "/openc3/lib/openc3/microservices",
|
268
266
|
parent: parent,
|
269
267
|
shard: @shard,
|
270
268
|
scope: @scope
|
@@ -280,7 +278,7 @@ module OpenC3
|
|
280
278
|
microservice = MicroserviceModel.new(
|
281
279
|
name: microservice_name,
|
282
280
|
cmd: ["ruby", "scope_cleanup_microservice.rb", microservice_name],
|
283
|
-
work_dir:
|
281
|
+
work_dir: "/openc3/lib/openc3/microservices",
|
284
282
|
parent: parent,
|
285
283
|
shard: @shard,
|
286
284
|
scope: @scope
|
@@ -296,7 +294,7 @@ module OpenC3
|
|
296
294
|
microservice = MicroserviceModel.new(
|
297
295
|
name: microservice_name,
|
298
296
|
cmd: ["ruby", "critical_cmd_microservice.rb", microservice_name],
|
299
|
-
work_dir:
|
297
|
+
work_dir: "/openc3-enterprise/lib/openc3-enterprise/microservices",
|
300
298
|
parent: parent,
|
301
299
|
shard: @shard,
|
302
300
|
scope: @scope
|
@@ -312,7 +310,7 @@ module OpenC3
|
|
312
310
|
microservice = MicroserviceModel.new(
|
313
311
|
name: microservice_name,
|
314
312
|
cmd: ["ruby", "multi_microservice.rb", *@children],
|
315
|
-
work_dir:
|
313
|
+
work_dir: "/openc3/lib/openc3/microservices",
|
316
314
|
target_names: [],
|
317
315
|
shard: @shard,
|
318
316
|
scope: @scope
|
@@ -323,15 +321,15 @@ module OpenC3
|
|
323
321
|
end
|
324
322
|
|
325
323
|
def deploy(gem_path, variables)
|
326
|
-
seed_database
|
324
|
+
seed_database
|
327
325
|
|
328
326
|
if ENTERPRISE
|
329
327
|
# Create DEFAULT trigger group model
|
330
|
-
model = TriggerGroupModel.get(name:
|
328
|
+
model = TriggerGroupModel.get(name: "DEFAULT", scope: @scope)
|
331
329
|
unless model
|
332
|
-
model = TriggerGroupModel.new(name:
|
333
|
-
model.create
|
334
|
-
model.deploy
|
330
|
+
model = TriggerGroupModel.new(name: "DEFAULT", shard: @shard, scope: @scope)
|
331
|
+
model.create
|
332
|
+
model.deploy
|
335
333
|
end
|
336
334
|
end
|
337
335
|
|
@@ -351,7 +349,7 @@ module OpenC3
|
|
351
349
|
deploy_unknown_packetlog_microservice(gem_path, variables, @parent)
|
352
350
|
|
353
351
|
# Only DEFAULT scope
|
354
|
-
if @scope ==
|
352
|
+
if @scope == "DEFAULT"
|
355
353
|
# Periodic Microservice
|
356
354
|
deploy_periodic_microservice(gem_path, variables, @parent)
|
357
355
|
end
|
@@ -403,14 +401,46 @@ module OpenC3
|
|
403
401
|
end
|
404
402
|
|
405
403
|
def seed_database
|
406
|
-
setting = SettingModel.get(name:
|
407
|
-
SettingModel.set({
|
408
|
-
setting = SettingModel.get(name:
|
409
|
-
SettingModel.set({
|
410
|
-
setting = SettingModel.get(name:
|
411
|
-
SettingModel.set({
|
404
|
+
setting = SettingModel.get(name: "source_url")
|
405
|
+
SettingModel.set({name: "source_url", data: "https://github.com/OpenC3/cosmos"}, scope: @scope) unless setting
|
406
|
+
setting = SettingModel.get(name: "rubygems_url")
|
407
|
+
SettingModel.set({name: "rubygems_url", data: ENV["RUBYGEMS_URL"] || "https://rubygems.org"}, scope: @scope) unless setting
|
408
|
+
setting = SettingModel.get(name: "pypi_url")
|
409
|
+
SettingModel.set({name: "pypi_url", data: ENV["PYPI_URL"] || "https://pypi.org"}, scope: @scope) unless setting
|
412
410
|
# Set the news feed to true by default, don't bother checking if it's already set
|
413
|
-
SettingModel.set({
|
411
|
+
SettingModel.set({name: "news_feed", data: true}, scope: @scope)
|
412
|
+
|
413
|
+
setting = SettingModel.get(name: "system_health")
|
414
|
+
system_health_data = {
|
415
|
+
"cpu" => {
|
416
|
+
"redThreshold" => 90.0,
|
417
|
+
"yellowThreshold" => 80.0,
|
418
|
+
"snoozeMinutes" => 15,
|
419
|
+
"lastTriggerTimeRed" => nil, # timestamp or nil
|
420
|
+
"lastTriggerTimeYellow" => nil, # timestamp or nil
|
421
|
+
"sustainedSeconds" => 15
|
422
|
+
},
|
423
|
+
"memory" => {
|
424
|
+
"redThreshold" => 90.0,
|
425
|
+
"yellowThreshold" => 80.0,
|
426
|
+
"snoozeMinutes" => 15,
|
427
|
+
"lastTriggerTimeRed" => nil, # timestamp or nil
|
428
|
+
"lastTriggerTimeYellow" => nil, # timestamp or nil
|
429
|
+
"sustainedSeconds" => 15
|
430
|
+
},
|
431
|
+
"disk" => {
|
432
|
+
"redThreshold" => 90.0,
|
433
|
+
"yellowThreshold" => 80.0,
|
434
|
+
"snoozeMinutes" => 720, # 12 hours
|
435
|
+
"lastTriggerTimeRed" => nil, # timestamp or nil
|
436
|
+
"lastTriggerTimeYellow" => nil, # timestamp or nil
|
437
|
+
"sustainedSeconds" => 60
|
438
|
+
},
|
439
|
+
"global" => {
|
440
|
+
"enableAlerts" => true
|
441
|
+
}
|
442
|
+
}
|
443
|
+
SettingModel.set({name: "system_health", data: system_health_data}, scope: "DEFAULT") unless setting
|
414
444
|
end
|
415
445
|
end
|
416
446
|
end
|
@@ -0,0 +1,93 @@
|
|
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/top_level'
|
20
|
+
require 'openc3/models/model'
|
21
|
+
require 'openc3/models/scope_model'
|
22
|
+
require 'openc3/utilities/bucket'
|
23
|
+
require 'openc3/utilities/bucket_utilities'
|
24
|
+
|
25
|
+
module OpenC3
|
26
|
+
class ScriptEngineModel < Model
|
27
|
+
PRIMARY_KEY = 'openc3_script_engines'
|
28
|
+
|
29
|
+
attr_accessor :filename # Script Engine filename
|
30
|
+
|
31
|
+
# NOTE: The following three class methods are used by the ModelController
|
32
|
+
# and are reimplemented to enable various Model class methods to work
|
33
|
+
def self.get(name:, scope: nil)
|
34
|
+
super(PRIMARY_KEY, name: name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.names(scope: nil)
|
38
|
+
array = []
|
39
|
+
all(scope: scope).each do |name, _script_engine|
|
40
|
+
array << name
|
41
|
+
end
|
42
|
+
array
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.all(scope: nil)
|
46
|
+
tools = Store.hgetall(PRIMARY_KEY)
|
47
|
+
tools.each do |key, value|
|
48
|
+
tools[key] = JSON.parse(value, :allow_nan => true, :create_additions => true)
|
49
|
+
end
|
50
|
+
return tools
|
51
|
+
end
|
52
|
+
|
53
|
+
# Called by the PluginModel to allow this class to validate it's top-level keyword: "SCRIPT_ENGINE"
|
54
|
+
def self.handle_config(parser, keyword, parameters, plugin: nil, needs_dependencies: false, scope:)
|
55
|
+
case keyword
|
56
|
+
when 'SCRIPT_ENGINE'
|
57
|
+
parser.verify_num_parameters(1, 3, "SCRIPT_ENGINE <Extension> <Filename>")
|
58
|
+
return self.new(name: parameters[0], plugin: plugin, filename: parameters[1], scope: scope)
|
59
|
+
else
|
60
|
+
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Script Engine: #{keyword} #{parameters.join(" ")}")
|
61
|
+
end
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(
|
66
|
+
name:,
|
67
|
+
updated_at: nil,
|
68
|
+
plugin: nil,
|
69
|
+
filename: nil,
|
70
|
+
scope:
|
71
|
+
)
|
72
|
+
super(PRIMARY_KEY, name: name, plugin: plugin, updated_at: updated_at, scope: scope)
|
73
|
+
@filename = filename
|
74
|
+
end
|
75
|
+
|
76
|
+
def as_json(*a)
|
77
|
+
{
|
78
|
+
'name' => @name,
|
79
|
+
'updated_at' => @updated_at,
|
80
|
+
'plugin' => @plugin,
|
81
|
+
'filename' => @filename
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_config(parser, keyword, parameters)
|
86
|
+
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Script Engine: #{keyword} #{parameters.join(" ")}")
|
87
|
+
end
|
88
|
+
|
89
|
+
def deploy(gem_path, variables, validate_only: false)
|
90
|
+
# Nothing to do
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -45,6 +45,7 @@ module OpenC3
|
|
45
45
|
attr_accessor :pid
|
46
46
|
attr_accessor :log
|
47
47
|
attr_accessor :report
|
48
|
+
attr_accessor :script_engine
|
48
49
|
|
49
50
|
# NOTE: The following three class methods are used by the ModelController
|
50
51
|
# and are reimplemented to enable various Model class methods to work
|
@@ -147,6 +148,7 @@ module OpenC3
|
|
147
148
|
pid: nil,
|
148
149
|
log: nil,
|
149
150
|
report: nil,
|
151
|
+
script_engine: nil,
|
150
152
|
updated_at: nil,
|
151
153
|
scope:
|
152
154
|
)
|
@@ -173,6 +175,7 @@ module OpenC3
|
|
173
175
|
@pid = pid
|
174
176
|
@log = log
|
175
177
|
@report = report
|
178
|
+
@script_engine = script_engine
|
176
179
|
end
|
177
180
|
|
178
181
|
def is_complete?
|
@@ -257,6 +260,7 @@ module OpenC3
|
|
257
260
|
'pid' => @pid,
|
258
261
|
'log' => @log,
|
259
262
|
'report' => @report,
|
263
|
+
'script_engine' => @script_engine,
|
260
264
|
'updated_at' => @updated_at,
|
261
265
|
'scope' => @scope
|
262
266
|
}
|
@@ -676,6 +676,8 @@ module OpenC3
|
|
676
676
|
LimitsEventTopic.delete(@name, scope: @scope)
|
677
677
|
Store.del("#{@scope}__openc3tlm__#{@name}")
|
678
678
|
Store.del("#{@scope}__openc3cmd__#{@name}")
|
679
|
+
Store.del("#{@scope}__TELEMETRYCNTS__{#{@name}}")
|
680
|
+
Store.del("#{@scope}__COMMANDCNTS__{#{@name}}")
|
679
681
|
|
680
682
|
# Note: these match the names of the services in deploy_microservices
|
681
683
|
%w(MULTI DECOM COMMANDLOG DECOMCMDLOG PACKETLOG DECOMLOG REDUCER CLEANUP).each do |type|
|
@@ -795,6 +797,7 @@ module OpenC3
|
|
795
797
|
if clear_old
|
796
798
|
Store.del("#{@scope}__openc3tlm__#{target_name}")
|
797
799
|
Store.del("#{@scope}__openc3tlm__#{target_name}__allitems")
|
800
|
+
Store.del("#{@scope}__TELEMETRYCNTS__{#{target_name}}")
|
798
801
|
end
|
799
802
|
packets.each do |packet_name, packet|
|
800
803
|
Logger.debug "Configuring tlm packet: #{target_name} #{packet_name}"
|
@@ -816,7 +819,10 @@ module OpenC3
|
|
816
819
|
|
817
820
|
def update_store_commands(packet_hash, clear_old: true)
|
818
821
|
packet_hash.each do |target_name, packets|
|
819
|
-
|
822
|
+
if clear_old
|
823
|
+
Store.del("#{@scope}__openc3cmd__#{target_name}")
|
824
|
+
Store.del("#{@scope}__COMMANDCNTS__{#{target_name}}")
|
825
|
+
end
|
820
826
|
packets.each do |packet_name, packet|
|
821
827
|
Logger.debug "Configuring cmd packet: #{target_name} #{packet_name}"
|
822
828
|
begin
|
data/lib/openc3/script/script.rb
CHANGED
@@ -226,7 +226,11 @@ module OpenC3
|
|
226
226
|
# script_run("INST/procedures/collect.rb")
|
227
227
|
#
|
228
228
|
def initialize_offline_access
|
229
|
-
|
229
|
+
keycloak_url = ENV['OPENC3_KEYCLOAK_URL']
|
230
|
+
if keycloak_url.nil? or keycloak_url.empty?
|
231
|
+
raise "initialize_offline_access only valid in COSMOS Enterprise. OPENC3_KEYCLOAK_URL environment variable must be set."
|
232
|
+
end
|
233
|
+
auth = OpenC3KeycloakAuthentication.new(keycloak_url)
|
230
234
|
auth.token(include_bearer: true, openid_scope: 'openid%20offline_access')
|
231
235
|
set_offline_access(auth.refresh_token)
|
232
236
|
end
|
@@ -0,0 +1,118 @@
|
|
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
|
+
module OpenC3
|
20
|
+
class ScriptEngine
|
21
|
+
attr_accessor :running_script
|
22
|
+
|
23
|
+
def initialize(running_script)
|
24
|
+
@running_script = running_script
|
25
|
+
end
|
26
|
+
|
27
|
+
# Override this method in the subclass to implement the script engine
|
28
|
+
def run_line(line, lines, filename, line_no)
|
29
|
+
puts line
|
30
|
+
return line_no + 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def run_text(text, filename: nil, line_no: 1, end_line_no: nil, bind_variables: false)
|
34
|
+
lines = text.lines
|
35
|
+
loop do
|
36
|
+
line = lines[line_no - 1]
|
37
|
+
return if line.nil?
|
38
|
+
|
39
|
+
begin
|
40
|
+
next_line_no = line_no + 1
|
41
|
+
running_script.pre_line_instrumentation(filename, line_no)
|
42
|
+
next_line_no = run_line(line, lines, filename, line_no)
|
43
|
+
running_script.post_line_instrumentation(filename, line_no)
|
44
|
+
rescue Exception => e
|
45
|
+
retry if running_script.exception_instrumentation(e, filename, line_no)
|
46
|
+
end
|
47
|
+
|
48
|
+
line_no = next_line_no
|
49
|
+
return if end_line_no and line_no > end_line_no
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def debug(text)
|
54
|
+
run_line(text, [text], "DEBUG", 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
def syntax_check(text, filename: nil)
|
58
|
+
puts "Not Implemented"
|
59
|
+
return 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def mnemonic_check(text, filename: nil)
|
63
|
+
puts "Not Implemented"
|
64
|
+
return 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def tokenizer(s, special_chars = '()><+-*/=;,')
|
68
|
+
result = []
|
69
|
+
i = 0
|
70
|
+
while i < s.length
|
71
|
+
# Skip whitespace
|
72
|
+
if s[i].match?(/\s/)
|
73
|
+
i += 1
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
# Handle quoted strings (single or double quotes)
|
78
|
+
if ['"', "'"].include?(s[i])
|
79
|
+
quote_char = s[i]
|
80
|
+
quote_start = i
|
81
|
+
i += 1
|
82
|
+
# Find the closing quote
|
83
|
+
while i < s.length
|
84
|
+
if s[i] == '\\' && i + 1 < s.length # Handle escaped characters
|
85
|
+
i += 2
|
86
|
+
elsif s[i] == quote_char # Found closing quote
|
87
|
+
i += 1
|
88
|
+
break
|
89
|
+
else
|
90
|
+
i += 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# Include the quotes in the token
|
94
|
+
result << s[quote_start...i]
|
95
|
+
next
|
96
|
+
end
|
97
|
+
|
98
|
+
# Handle special characters
|
99
|
+
if special_chars.include?(s[i])
|
100
|
+
result << s[i]
|
101
|
+
i += 1
|
102
|
+
next
|
103
|
+
end
|
104
|
+
|
105
|
+
# Handle regular tokens
|
106
|
+
token_start = i
|
107
|
+
while i < s.length && !s[i].match?(/\s/) && !special_chars.include?(s[i]) && !['"', "'"].include?(s[i])
|
108
|
+
i += 1
|
109
|
+
end
|
110
|
+
if i > token_start
|
111
|
+
result << s[token_start...i]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
return result
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -24,6 +24,8 @@ require 'openc3/topics/topic'
|
|
24
24
|
|
25
25
|
module OpenC3
|
26
26
|
class InterfaceTopic < Topic
|
27
|
+
COMMAND_ACK_TIMEOUT_S = 30
|
28
|
+
|
27
29
|
# Generate a list of topics for this interface. This includes the interface itself
|
28
30
|
# and all the targets which are assigned to this interface.
|
29
31
|
def self.topics(interface, scope:)
|
@@ -48,9 +50,27 @@ module OpenC3
|
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
|
-
def self.write_raw(interface_name, data, scope:)
|
52
|
-
|
53
|
-
|
53
|
+
def self.write_raw(interface_name, data, timeout: nil, scope:)
|
54
|
+
interface_name = interface_name.upcase
|
55
|
+
|
56
|
+
timeout = COMMAND_ACK_TIMEOUT_S unless timeout
|
57
|
+
ack_topic = "{#{scope}__ACKCMD}INTERFACE__#{interface_name}"
|
58
|
+
Topic.update_topic_offsets([ack_topic])
|
59
|
+
|
60
|
+
cmd_id = Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'raw' => data }, '*', 100)
|
61
|
+
time = Time.now
|
62
|
+
while (Time.now - time) < timeout
|
63
|
+
Topic.read_topics([ack_topic]) do |_topic, _msg_id, msg_hash, _redis|
|
64
|
+
if msg_hash["id"] == cmd_id
|
65
|
+
if msg_hash["result"] == "SUCCESS"
|
66
|
+
return
|
67
|
+
else
|
68
|
+
raise msg_hash["result"]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
raise "Timeout of #{timeout}s waiting for cmd ack"
|
54
74
|
end
|
55
75
|
|
56
76
|
def self.connect_interface(interface_name, *interface_params, scope:)
|