openc3 6.4.1 → 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 +274 -0
- data/data/config/item_modifiers.yaml +8 -70
- data/data/config/parameter_modifiers.yaml +9 -69
- 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/api.rb +1 -1
- data/lib/openc3/api/tlm_api.rb +10 -5
- data/lib/openc3/conversions/unix_time_conversion.rb +2 -2
- data/lib/openc3/conversions/unix_time_formatted_conversion.rb +3 -3
- data/lib/openc3/conversions/unix_time_seconds_conversion.rb +3 -3
- data/lib/openc3/core_ext/time.rb +2 -9
- data/lib/openc3/microservices/cleanup_microservice.rb +2 -2
- data/lib/openc3/microservices/decom_microservice.rb +2 -2
- data/lib/openc3/microservices/interface_microservice.rb +18 -12
- data/lib/openc3/microservices/log_microservice.rb +4 -2
- data/lib/openc3/microservices/multi_microservice.rb +2 -2
- data/lib/openc3/microservices/periodic_microservice.rb +2 -2
- data/lib/openc3/microservices/reducer_microservice.rb +2 -2
- data/lib/openc3/microservices/router_microservice.rb +2 -2
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +2 -2
- data/lib/openc3/microservices/text_log_microservice.rb +19 -6
- 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 +26 -4
- data/lib/openc3/models/target_model.rb +33 -5
- data/lib/openc3/packets/packet_config.rb +1 -1
- data/lib/openc3/script/autonomic.rb +359 -0
- data/lib/openc3/script/script.rb +6 -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 +9 -1
@@ -0,0 +1,359 @@
|
|
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
|
+
module Script
|
21
|
+
private
|
22
|
+
|
23
|
+
# Group Methods
|
24
|
+
def autonomic_group_list(scope: $openc3_scope)
|
25
|
+
begin
|
26
|
+
endpoint = "/openc3-api/autonomic/group"
|
27
|
+
response = $api_server.request('get', endpoint, scope: scope)
|
28
|
+
if response.nil? || response.status != 200
|
29
|
+
raise "Unexpected response to autonomic_group_list: #{response.inspect}"
|
30
|
+
end
|
31
|
+
return JSON.parse(response.body)
|
32
|
+
rescue => error
|
33
|
+
raise "autonomic_group_list failed due to #{error.formatted}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def autonomic_group_create(name, scope: $openc3_scope)
|
38
|
+
begin
|
39
|
+
endpoint = "/openc3-api/autonomic/group"
|
40
|
+
response = $api_server.request('post', endpoint, data: { name: name }, json: true, scope: scope)
|
41
|
+
if response.nil? || response.status != 201
|
42
|
+
if response
|
43
|
+
parsed = JSON.parse(response.body)
|
44
|
+
raise "autonomic_group_create error: #{parsed['message']}"
|
45
|
+
else
|
46
|
+
raise "autonomic_group_create failed"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
return JSON.parse(response.body)
|
50
|
+
rescue => error
|
51
|
+
raise "autonomic_group_create failed due to #{error.formatted}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def autonomic_group_show(name, scope: $openc3_scope)
|
56
|
+
begin
|
57
|
+
endpoint = "/openc3-api/autonomic/group/#{name}"
|
58
|
+
response = $api_server.request('get', endpoint, scope: scope)
|
59
|
+
if response.nil? || response.status != 200
|
60
|
+
raise "Unexpected response to autonomic_group_show: #{response.inspect}"
|
61
|
+
end
|
62
|
+
return JSON.parse(response.body)
|
63
|
+
rescue => error
|
64
|
+
raise "autonomic_group_show failed due to #{error.formatted}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def autonomic_group_destroy(name, scope: $openc3_scope)
|
69
|
+
begin
|
70
|
+
endpoint = "/openc3-api/autonomic/group/#{name}"
|
71
|
+
response = $api_server.request('delete', endpoint, scope: scope)
|
72
|
+
if response.nil? || response.status != 200
|
73
|
+
if response
|
74
|
+
parsed = JSON.parse(response.body)
|
75
|
+
raise "autonomic_group_destroy error: #{parsed['error']}"
|
76
|
+
else
|
77
|
+
raise "autonomic_group_destroy failed"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
return
|
81
|
+
rescue => error
|
82
|
+
raise "autonomic_group_destroy failed due to #{error.formatted}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Trigger Methods
|
87
|
+
def autonomic_trigger_list(group: 'DEFAULT', scope: $openc3_scope)
|
88
|
+
begin
|
89
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger"
|
90
|
+
response = $api_server.request('get', endpoint, scope: scope)
|
91
|
+
if response.nil? || response.status != 200
|
92
|
+
raise "Unexpected response to autonomic_trigger_list: #{response.inspect}"
|
93
|
+
end
|
94
|
+
return JSON.parse(response.body)
|
95
|
+
rescue => error
|
96
|
+
raise "autonomic_trigger_list failed due to #{error.formatted}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def autonomic_trigger_create(left:, operator:, right:, group: 'DEFAULT', scope: $openc3_scope)
|
101
|
+
begin
|
102
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger"
|
103
|
+
config = {
|
104
|
+
'group' => group,
|
105
|
+
'left' => left,
|
106
|
+
'operator' => operator,
|
107
|
+
'right' => right
|
108
|
+
}
|
109
|
+
response = $api_server.request('post', endpoint, data: config, json: true, scope: scope)
|
110
|
+
if response.nil? || response.status != 201
|
111
|
+
if response
|
112
|
+
parsed = JSON.parse(response.body)
|
113
|
+
raise "autonomic_trigger_create error: #{parsed['error']}"
|
114
|
+
else
|
115
|
+
raise "autonomic_trigger_create failed"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
return JSON.parse(response.body)
|
119
|
+
rescue => error
|
120
|
+
raise "autonomic_trigger_create failed due to #{error.formatted}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def autonomic_trigger_show(name, group: 'DEFAULT', scope: $openc3_scope)
|
125
|
+
begin
|
126
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger/#{name}"
|
127
|
+
response = $api_server.request('get', endpoint, scope: scope)
|
128
|
+
if response.nil? || response.status != 200
|
129
|
+
raise "Unexpected response to autonomic_trigger_show: #{response.inspect}"
|
130
|
+
end
|
131
|
+
return JSON.parse(response.body)
|
132
|
+
rescue => error
|
133
|
+
raise "autonomic_trigger_show failed due to #{error.formatted}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def autonomic_trigger_enable(name, group: 'DEFAULT', scope: $openc3_scope)
|
138
|
+
begin
|
139
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger/#{name}/enable"
|
140
|
+
response = $api_server.request('post', endpoint, json: true, scope: scope)
|
141
|
+
if response.nil? || response.status != 200
|
142
|
+
if response
|
143
|
+
parsed = JSON.parse(response.body)
|
144
|
+
raise "autonomic_trigger_enable error: #{parsed['error']}"
|
145
|
+
else
|
146
|
+
raise "autonomic_trigger_enable failed"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
return
|
150
|
+
rescue => error
|
151
|
+
raise "autonomic_trigger_enable failed due to #{error.formatted}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def autonomic_trigger_disable(name, group: 'DEFAULT', scope: $openc3_scope)
|
156
|
+
begin
|
157
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger/#{name}/disable"
|
158
|
+
response = $api_server.request('post', endpoint, json: true, scope: scope)
|
159
|
+
if response.nil? || response.status != 200
|
160
|
+
if response
|
161
|
+
parsed = JSON.parse(response.body)
|
162
|
+
raise "autonomic_trigger_disable error: #{parsed['error']}"
|
163
|
+
else
|
164
|
+
raise "autonomic_trigger_disable failed"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
return
|
168
|
+
rescue => error
|
169
|
+
raise "autonomic_trigger_disable failed due to #{error.formatted}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def autonomic_trigger_update(name, group: 'DEFAULT', left: nil, operator: nil, right: nil, scope: $openc3_scope)
|
174
|
+
begin
|
175
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger/#{name}"
|
176
|
+
config = {}
|
177
|
+
config['left'] = left if left
|
178
|
+
config['operator'] = operator if operator
|
179
|
+
config['right'] = right if right
|
180
|
+
response = $api_server.request('put', endpoint, data: config, json: true, scope: scope)
|
181
|
+
if response.nil? || response.status != 200
|
182
|
+
if response
|
183
|
+
parsed = JSON.parse(response.body)
|
184
|
+
raise "autonomic_trigger_update error: #{parsed['error']}"
|
185
|
+
else
|
186
|
+
raise "autonomic_trigger_update failed"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
return JSON.parse(response.body)
|
190
|
+
rescue => error
|
191
|
+
raise "autonomic_trigger_update failed due to #{error.formatted}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def autonomic_trigger_destroy(name, group: 'DEFAULT', scope: $openc3_scope)
|
196
|
+
begin
|
197
|
+
endpoint = "/openc3-api/autonomic/#{group}/trigger/#{name}"
|
198
|
+
response = $api_server.request('delete', endpoint, scope: scope)
|
199
|
+
if response.nil? || response.status != 200
|
200
|
+
if response
|
201
|
+
parsed = JSON.parse(response.body)
|
202
|
+
raise "autonomic_trigger_destroy error: #{parsed['error']}"
|
203
|
+
else
|
204
|
+
raise "autonomic_trigger_destroy failed"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
return
|
208
|
+
rescue => error
|
209
|
+
raise "autonomic_trigger_destroy failed due to #{error.formatted}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Reaction Methods
|
214
|
+
def autonomic_reaction_list(scope: $openc3_scope)
|
215
|
+
begin
|
216
|
+
endpoint = "/openc3-api/autonomic/reaction"
|
217
|
+
response = $api_server.request('get', endpoint, scope: scope)
|
218
|
+
if response.nil? || response.status != 200
|
219
|
+
raise "Unexpected response to autonomic_reaction_list: #{response.inspect}"
|
220
|
+
end
|
221
|
+
return JSON.parse(response.body)
|
222
|
+
rescue => error
|
223
|
+
raise "autonomic_reaction_list failed due to #{error.formatted}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def autonomic_reaction_create(triggers:, actions:, trigger_level: 'EDGE', snooze: 0, scope: $openc3_scope)
|
228
|
+
begin
|
229
|
+
endpoint = "/openc3-api/autonomic/reaction"
|
230
|
+
config = {
|
231
|
+
'triggers' => triggers,
|
232
|
+
'actions' => actions,
|
233
|
+
'trigger_level' => trigger_level,
|
234
|
+
'snooze' => snooze,
|
235
|
+
}
|
236
|
+
response = $api_server.request('post', endpoint, data: config, json: true, scope: scope)
|
237
|
+
if response.nil? || response.status != 201
|
238
|
+
if response
|
239
|
+
parsed = JSON.parse(response.body)
|
240
|
+
raise "autonomic_reaction_create error: #{parsed['error']}"
|
241
|
+
else
|
242
|
+
raise "autonomic_reaction_create failed"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
return JSON.parse(response.body)
|
246
|
+
rescue => error
|
247
|
+
raise "autonomic_reaction_create failed due to #{error.formatted}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def autonomic_reaction_show(name, scope: $openc3_scope)
|
252
|
+
begin
|
253
|
+
endpoint = "/openc3-api/autonomic/reaction/#{name}"
|
254
|
+
response = $api_server.request('get', endpoint, scope: scope)
|
255
|
+
if response.nil? || response.status != 200
|
256
|
+
raise "Unexpected response to autonomic_reaction_show: #{response.inspect}"
|
257
|
+
end
|
258
|
+
return JSON.parse(response.body)
|
259
|
+
rescue => error
|
260
|
+
raise "autonomic_reaction_show failed due to #{error.formatted}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def autonomic_reaction_enable(name, scope: $openc3_scope)
|
265
|
+
begin
|
266
|
+
endpoint = "/openc3-api/autonomic/reaction/#{name}/enable"
|
267
|
+
response = $api_server.request('post', endpoint, json: true, scope: scope)
|
268
|
+
if response.nil? || response.status != 200
|
269
|
+
if response
|
270
|
+
parsed = JSON.parse(response.body)
|
271
|
+
raise "autonomic_reaction_enable error: #{parsed['error']}"
|
272
|
+
else
|
273
|
+
raise "autonomic_reaction_enable failed"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
return
|
277
|
+
rescue => error
|
278
|
+
raise "autonomic_reaction_enable failed due to #{error.formatted}"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def autonomic_reaction_disable(name, scope: $openc3_scope)
|
283
|
+
begin
|
284
|
+
endpoint = "/openc3-api/autonomic/reaction/#{name}/disable"
|
285
|
+
response = $api_server.request('post', endpoint, json: true, scope: scope)
|
286
|
+
if response.nil? || response.status != 200
|
287
|
+
if response
|
288
|
+
parsed = JSON.parse(response.body)
|
289
|
+
raise "autonomic_reaction_disable error: #{parsed['error']}"
|
290
|
+
else
|
291
|
+
raise "autonomic_reaction_disable failed"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
return
|
295
|
+
rescue => error
|
296
|
+
raise "autonomic_reaction_disable failed due to #{error.formatted}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def autonomic_reaction_execute(name, scope: $openc3_scope)
|
301
|
+
begin
|
302
|
+
endpoint = "/openc3-api/autonomic/reaction/#{name}/execute"
|
303
|
+
response = $api_server.request('post', endpoint, json: true, scope: scope)
|
304
|
+
if response.nil? || response.status != 200
|
305
|
+
if response
|
306
|
+
parsed = JSON.parse(response.body)
|
307
|
+
raise "autonomic_reaction_execute error: #{parsed['error']}"
|
308
|
+
else
|
309
|
+
raise "autonomic_reaction_execute failed"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
return
|
313
|
+
rescue => error
|
314
|
+
raise "autonomic_reaction_execute failed due to #{error.formatted}"
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def autonomic_reaction_update(name, triggers: nil, actions: nil, trigger_level: nil, snooze: nil, scope: $openc3_scope)
|
319
|
+
begin
|
320
|
+
endpoint = "/openc3-api/autonomic/reaction/#{name}"
|
321
|
+
config = {}
|
322
|
+
config['triggers'] = triggers if triggers
|
323
|
+
config['actions'] = actions if actions
|
324
|
+
config['trigger_level'] = trigger_level if trigger_level
|
325
|
+
config['snooze'] = snooze if snooze
|
326
|
+
response = $api_server.request('put', endpoint, data: config, json: true, scope: scope)
|
327
|
+
if response.nil? || response.status != 200
|
328
|
+
if response
|
329
|
+
parsed = JSON.parse(response.body)
|
330
|
+
raise "autonomic_reaction_update error: #{parsed['error']}"
|
331
|
+
else
|
332
|
+
raise "autonomic_reaction_update failed"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
return JSON.parse(response.body)
|
336
|
+
rescue => error
|
337
|
+
raise "autonomic_reaction_update failed due to #{error.formatted}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def autonomic_reaction_destroy(name, scope: $openc3_scope)
|
342
|
+
begin
|
343
|
+
endpoint = "/openc3-api/autonomic/reaction/#{name}"
|
344
|
+
response = $api_server.request('delete', endpoint, scope: scope)
|
345
|
+
if response.nil? || response.status != 200
|
346
|
+
if response
|
347
|
+
parsed = JSON.parse(response.body)
|
348
|
+
raise "autonomic_reaction_destroy error: #{parsed['error']}"
|
349
|
+
else
|
350
|
+
raise "autonomic_reaction_destroy failed"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
return
|
354
|
+
rescue => error
|
355
|
+
raise "autonomic_reaction_destroy failed due to #{error.formatted}"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
data/lib/openc3/script/script.rb
CHANGED
@@ -24,6 +24,7 @@ require 'openc3'
|
|
24
24
|
require 'openc3/api/api'
|
25
25
|
require 'openc3/io/json_drb_object'
|
26
26
|
require 'openc3/script/api_shared'
|
27
|
+
require 'openc3/script/autonomic'
|
27
28
|
require 'openc3/script/calendar'
|
28
29
|
require 'openc3/script/commands'
|
29
30
|
require 'openc3/script/critical_cmd'
|
@@ -225,7 +226,11 @@ module OpenC3
|
|
225
226
|
# script_run("INST/procedures/collect.rb")
|
226
227
|
#
|
227
228
|
def initialize_offline_access
|
228
|
-
|
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)
|
229
234
|
auth.token(include_bearer: true, openid_scope: 'openid%20offline_access')
|
230
235
|
set_offline_access(auth.refresh_token)
|
231
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:)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: ascii-8bit
|
2
2
|
|
3
|
-
# Copyright
|
3
|
+
# Copyright 2025 OpenC3, Inc.
|
4
4
|
# All Rights Reserved.
|
5
5
|
#
|
6
6
|
# This program is free software; you can modify and/or redistribute it
|
@@ -18,7 +18,7 @@
|
|
18
18
|
|
19
19
|
module OpenC3
|
20
20
|
class CliGenerator
|
21
|
-
GENERATORS = %w(plugin target microservice widget conversion limits_response tool tool_vue tool_angular tool_react tool_svelte)
|
21
|
+
GENERATORS = %w(plugin target microservice widget conversion processor limits_response tool tool_vue tool_angular tool_react tool_svelte)
|
22
22
|
TEMPLATES_DIR = "#{File.dirname(__FILE__)}/../../../templates"
|
23
23
|
|
24
24
|
# Called by openc3cli with ARGV[1..-1]
|
@@ -32,8 +32,8 @@ module OpenC3
|
|
32
32
|
|
33
33
|
def self.check_args(args)
|
34
34
|
args.each do |arg|
|
35
|
-
if arg =~ /\s/
|
36
|
-
abort("#{args[0].to_s.downcase} arguments can not have spaces!")
|
35
|
+
if arg =~ /\s/ and args[0].to_s.downcase[0..3] != 'tool'
|
36
|
+
abort("#{args[0].to_s.downcase} arguments can not have spaces!")
|
37
37
|
end
|
38
38
|
end
|
39
39
|
# All generators except 'plugin' must be within an existing plugin
|
@@ -58,12 +58,12 @@ module OpenC3
|
|
58
58
|
def self.process_template(template_dir, the_binding)
|
59
59
|
Dir.glob("#{template_dir}/**/*", File::FNM_DOTMATCH).each do |file|
|
60
60
|
next if File.basename(file) == '.'
|
61
|
-
if @@language == 'rb'
|
61
|
+
if @@language == 'rb' and File.extname(file) == '.py'
|
62
62
|
# Ignore python files if we're ruby
|
63
|
-
next
|
64
|
-
elsif @@language == 'py'
|
63
|
+
next
|
64
|
+
elsif @@language == 'py' and File.extname(file) == '.rb'
|
65
65
|
# Ignore ruby files if we're python
|
66
|
-
next
|
66
|
+
next
|
67
67
|
end
|
68
68
|
base_name = file.sub("#{template_dir}/", '')
|
69
69
|
next if yield base_name
|
@@ -113,8 +113,8 @@ module OpenC3
|
|
113
113
|
abort("Target #{target_path} already exists!")
|
114
114
|
end
|
115
115
|
target_lib_filename = "#{target_name.downcase}.#{@@language}"
|
116
|
-
target_class = target_lib_filename.filename_to_class_name
|
117
|
-
target_object = target_name.downcase
|
116
|
+
target_class = target_lib_filename.filename_to_class_name # NOSONAR
|
117
|
+
target_object = target_name.downcase # NOSONAR
|
118
118
|
|
119
119
|
process_template("#{TEMPLATES_DIR}/target", binding) do |filename|
|
120
120
|
# Rename the template TARGET to our actual target named after the plugin
|
@@ -175,7 +175,7 @@ module OpenC3
|
|
175
175
|
abort("Microservice #{microservice_path} already exists!")
|
176
176
|
end
|
177
177
|
microservice_filename = "#{microservice_name.downcase}.#{@@language}"
|
178
|
-
microservice_class = microservice_filename.filename_to_class_name
|
178
|
+
microservice_class = microservice_filename.filename_to_class_name # NOSONAR
|
179
179
|
|
180
180
|
process_template("#{TEMPLATES_DIR}/microservice", binding) do |filename|
|
181
181
|
# Rename the template MICROSERVICE to our actual microservice name
|
@@ -310,9 +310,8 @@ module OpenC3
|
|
310
310
|
abort("Target '#{target_name}' does not exist! Conversions must be created for existing targets.")
|
311
311
|
end
|
312
312
|
conversion_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_CONVERSION"
|
313
|
-
conversion_path = "targets/#{target_name}/lib/"
|
314
313
|
conversion_basename = "#{conversion_name.downcase}.#{@@language}"
|
315
|
-
conversion_class = conversion_basename.filename_to_class_name
|
314
|
+
conversion_class = conversion_basename.filename_to_class_name # NOSONAR
|
316
315
|
conversion_filename = "targets/#{target_name}/lib/#{conversion_basename}"
|
317
316
|
if File.exist?(conversion_filename)
|
318
317
|
abort("Conversion #{conversion_filename} already exists!")
|
@@ -329,6 +328,35 @@ module OpenC3
|
|
329
328
|
return conversion_name
|
330
329
|
end
|
331
330
|
|
331
|
+
def self.generate_processor(args)
|
332
|
+
if args.length < 3 or args.length > 4
|
333
|
+
abort("Usage: cli generate processor <TARGET> <NAME> (--ruby or --python)")
|
334
|
+
end
|
335
|
+
|
336
|
+
# Create the local variables
|
337
|
+
target_name = args[1].upcase
|
338
|
+
unless File.exist?("targets/#{target_name}")
|
339
|
+
abort("Target '#{target_name}' does not exist! Processors must be created for existing targets.")
|
340
|
+
end
|
341
|
+
processor_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_PROCESSOR"
|
342
|
+
processor_basename = "#{processor_name.downcase}.#{@@language}"
|
343
|
+
processor_class = processor_basename.filename_to_class_name # NOSONAR
|
344
|
+
processor_filename = "targets/#{target_name}/lib/#{processor_basename}"
|
345
|
+
if File.exist?(processor_filename)
|
346
|
+
abort("Processor #{processor_filename} already exists!")
|
347
|
+
end
|
348
|
+
|
349
|
+
process_template("#{TEMPLATES_DIR}/processor", binding) do |filename|
|
350
|
+
filename.sub!("processor.#{@@language}", processor_filename)
|
351
|
+
false
|
352
|
+
end
|
353
|
+
|
354
|
+
puts "Processor #{processor_filename} successfully generated!"
|
355
|
+
puts "To use the processor add the following to a telemetry packet:"
|
356
|
+
puts " PROCESSOR #{args[2].upcase} #{processor_basename} <PARAMS...>"
|
357
|
+
return processor_name
|
358
|
+
end
|
359
|
+
|
332
360
|
def self.generate_limits_response(args)
|
333
361
|
if args.length < 3 or args.length > 4
|
334
362
|
abort("Usage: cli generate limits_response <TARGET> <NAME> (--ruby or --python)")
|
@@ -340,10 +368,9 @@ module OpenC3
|
|
340
368
|
abort("Target '#{target_name}' does not exist! Limits responses must be created for existing targets.")
|
341
369
|
end
|
342
370
|
response_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_LIMITS_RESPONSE"
|
343
|
-
response_path = "targets/#{target_name}/lib/"
|
344
371
|
response_basename = "#{response_name.downcase}.#{@@language}"
|
345
|
-
response_class = response_basename.filename_to_class_name
|
346
372
|
response_filename = "targets/#{target_name}/lib/#{response_basename}"
|
373
|
+
response_class = response_basename.filename_to_class_name # NOSONAR
|
347
374
|
if File.exist?(response_filename)
|
348
375
|
abort("response #{response_filename} already exists!")
|
349
376
|
end
|