openc3 6.2.0 → 6.3.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/Gemfile +2 -3
- data/bin/openc3cli +12 -3
- data/data/config/widgets.yaml +3 -0
- data/lib/openc3/api/cmd_api.rb +5 -8
- data/lib/openc3/api/interface_api.rb +8 -4
- data/lib/openc3/api/tlm_api.rb +23 -8
- data/lib/openc3/io/json_api_object.rb +1 -1
- data/lib/openc3/io/json_drb_object.rb +1 -1
- data/lib/openc3/microservices/interface_decom_common.rb +4 -1
- data/lib/openc3/microservices/interface_microservice.rb +68 -5
- data/lib/openc3/microservices/microservice.rb +1 -0
- data/lib/openc3/microservices/multi_microservice.rb +1 -1
- data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +24 -0
- data/lib/openc3/models/offline_access_model.rb +7 -6
- data/lib/openc3/models/scope_model.rb +5 -2
- data/lib/openc3/models/target_model.rb +150 -15
- data/lib/openc3/packets/commands.rb +10 -3
- data/lib/openc3/script/commands.rb +1 -1
- data/lib/openc3/script/script.rb +14 -0
- data/lib/openc3/utilities/authentication.rb +5 -5
- data/lib/openc3/utilities/local_mode.rb +5 -2
- data/lib/openc3/utilities/ruby_lex_utils.rb +114 -279
- data/lib/openc3/utilities/target_file.rb +6 -2
- data/lib/openc3/version.rb +5 -5
- 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 +20 -23
- data/ext/mkrf_conf.rb +0 -52
@@ -14,11 +14,17 @@
|
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
|
16
16
|
# Modified by OpenC3, Inc.
|
17
|
-
# All changes Copyright
|
17
|
+
# All changes Copyright 2025, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
|
+
#
|
23
|
+
# A portion of this file was funded by Blue Origin Enterprises, L.P.
|
24
|
+
# See https://github.com/OpenC3/cosmos/pull/1953 and https://github.com/OpenC3/cosmos/pull/1963
|
25
|
+
|
26
|
+
# A portion of this file was funded by Blue Origin Enterprises, L.P.
|
27
|
+
# See https://github.com/OpenC3/cosmos/pull/1957
|
22
28
|
|
23
29
|
require 'openc3/top_level'
|
24
30
|
require 'openc3/models/model'
|
@@ -204,7 +210,7 @@ module OpenC3
|
|
204
210
|
JSON.parse(json, :allow_nan => true, :create_additions => true)
|
205
211
|
end
|
206
212
|
|
207
|
-
# @return [Array
|
213
|
+
# @return [Array<Hash>] All packet hashes under the target_name
|
208
214
|
def self.packets(target_name, type: :TLM, scope:)
|
209
215
|
raise "Unknown type #{type} for #{target_name}" unless VALID_TYPES.include?(type)
|
210
216
|
raise "Target '#{target_name}' does not exist for scope: #{scope}" unless get(name: target_name, scope: scope)
|
@@ -217,7 +223,7 @@ module OpenC3
|
|
217
223
|
result
|
218
224
|
end
|
219
225
|
|
220
|
-
# @return [Array
|
226
|
+
# @return [Array<Hash>] All packet hashes under the target_name
|
221
227
|
def self.all_packet_name_descriptions(target_name, type: :TLM, scope:)
|
222
228
|
self.packets(target_name, type: type, scope: scope).map! { |hash| hash.slice("packet_name", "description") }
|
223
229
|
end
|
@@ -257,6 +263,28 @@ module OpenC3
|
|
257
263
|
found
|
258
264
|
end
|
259
265
|
|
266
|
+
# @return [Array<String>] All the item names for every packet in a target
|
267
|
+
def self.all_item_names(target_name, type: :TLM, scope:)
|
268
|
+
items = Store.zrange("#{scope}__openc3tlm__#{target_name}__allitems", 0, -1)
|
269
|
+
items = rebuild_target_allitems_list(target_name, type: type, scope: scope) if items.empty?
|
270
|
+
items
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.rebuild_target_allitems_list(target_name, type: :TLM, scope:)
|
274
|
+
packets = packets(target_name, type: type, scope: scope)
|
275
|
+
packets.each do |packet|
|
276
|
+
packet['items'].each do |item|
|
277
|
+
TargetModel.add_to_target_allitems_list(target_name, item['name'], scope: scope)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
Store.zrange("#{scope}__openc3tlm__#{target_name}__allitems", 0, -1) # return the new sorted set to let redis do the sorting
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.add_to_target_allitems_list(target_name, item_name, scope:)
|
284
|
+
score = 0 # https://redis.io/docs/latest/develop/data-types/sorted-sets/#lexicographical-scores
|
285
|
+
Store.zadd("#{scope}__openc3tlm__#{target_name}__allitems", score, item_name)
|
286
|
+
end
|
287
|
+
|
260
288
|
# @return [Hash{String => Array<Array<String, String, String>>}]
|
261
289
|
def self.limits_groups(scope:)
|
262
290
|
groups = Store.hgetall("#{scope}__limits_groups")
|
@@ -310,6 +338,7 @@ module OpenC3
|
|
310
338
|
end
|
311
339
|
end
|
312
340
|
|
341
|
+
# Make sure to update target_model.py if you add additional parameters
|
313
342
|
def initialize(
|
314
343
|
name:,
|
315
344
|
folder_name: nil,
|
@@ -763,7 +792,10 @@ module OpenC3
|
|
763
792
|
|
764
793
|
def update_store_telemetry(packet_hash, clear_old: true)
|
765
794
|
packet_hash.each do |target_name, packets|
|
766
|
-
|
795
|
+
if clear_old
|
796
|
+
Store.del("#{@scope}__openc3tlm__#{target_name}")
|
797
|
+
Store.del("#{@scope}__openc3tlm__#{target_name}__allitems")
|
798
|
+
end
|
767
799
|
packets.each do |packet_name, packet|
|
768
800
|
Logger.debug "Configuring tlm packet: #{target_name} #{packet_name}"
|
769
801
|
begin
|
@@ -775,6 +807,7 @@ module OpenC3
|
|
775
807
|
json_hash = Hash.new
|
776
808
|
packet.sorted_items.each do |item|
|
777
809
|
json_hash[item.name] = nil
|
810
|
+
TargetModel.add_to_target_allitems_list(target_name, item.name, scope: @scope)
|
778
811
|
end
|
779
812
|
CvtModel.set(json_hash, target_name: packet.target_name, packet_name: packet.packet_name, scope: @scope)
|
780
813
|
end
|
@@ -834,6 +867,8 @@ module OpenC3
|
|
834
867
|
return system
|
835
868
|
end
|
836
869
|
|
870
|
+
# NOTE: If you call dynamic_update multiple times you should specify a different
|
871
|
+
# filename parameter or the last one will be overwritten
|
837
872
|
def dynamic_update(packets, cmd_or_tlm = :TELEMETRY, filename = "dynamic_tlm.txt")
|
838
873
|
# Build hash of targets/packets
|
839
874
|
packet_hash = {}
|
@@ -862,17 +897,15 @@ module OpenC3
|
|
862
897
|
config << "\n"
|
863
898
|
end
|
864
899
|
configs.each do |target_name, config|
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
)
|
875
|
-
end
|
900
|
+
bucket_key = "#{@scope}/targets_modified/#{target_name}/cmd_tlm/#{filename}"
|
901
|
+
client = Bucket.getClient()
|
902
|
+
client.put_object(
|
903
|
+
# Use targets_modified to save modifications
|
904
|
+
# This keeps the original target clean (read-only)
|
905
|
+
bucket: ENV['OPENC3_CONFIG_BUCKET'],
|
906
|
+
key: bucket_key,
|
907
|
+
body: config
|
908
|
+
)
|
876
909
|
end
|
877
910
|
|
878
911
|
# Inform microservices of new topics
|
@@ -1233,5 +1266,107 @@ module OpenC3
|
|
1233
1266
|
deploy_multi_microservice(gem_path, variables)
|
1234
1267
|
end
|
1235
1268
|
end
|
1269
|
+
|
1270
|
+
def self.increment_telemetry_count(target_name, packet_name, count, scope:)
|
1271
|
+
result = Store.hincrby("#{scope}__TELEMETRYCNTS__{#{target_name}}", packet_name, count)
|
1272
|
+
if String === result
|
1273
|
+
return result.to_i
|
1274
|
+
else
|
1275
|
+
return result
|
1276
|
+
end
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
def self.get_all_telemetry_counts(target_name, scope:)
|
1280
|
+
result = {}
|
1281
|
+
get_all = Store.hgetall("#{scope}__TELEMETRYCNTS__{#{target_name}}")
|
1282
|
+
if Hash === get_all
|
1283
|
+
get_all.each do |key, value|
|
1284
|
+
result[key] = value.to_i
|
1285
|
+
end
|
1286
|
+
else
|
1287
|
+
return result
|
1288
|
+
end
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
def self.get_telemetry_count(target_name, packet_name, scope:)
|
1292
|
+
value = Store.hget("#{scope}__TELEMETRYCNTS__{#{target_name}}", packet_name)
|
1293
|
+
if String === value
|
1294
|
+
return value.to_i
|
1295
|
+
elsif value.nil?
|
1296
|
+
return 0 # Return 0 if the key doesn't exist
|
1297
|
+
else
|
1298
|
+
return value
|
1299
|
+
end
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
def self.get_telemetry_counts(target_packets, scope:)
|
1303
|
+
result = Store.redis_pool.pipelined do
|
1304
|
+
target_packets.each do |target_name, packet_name|
|
1305
|
+
target_name = target_name.upcase
|
1306
|
+
packet_name = packet_name.upcase
|
1307
|
+
Store.hget("#{scope}__TELEMETRYCNTS__{#{target_name}}", packet_name)
|
1308
|
+
end
|
1309
|
+
end
|
1310
|
+
counts = []
|
1311
|
+
result.each do |count|
|
1312
|
+
if count
|
1313
|
+
counts << count.to_i
|
1314
|
+
else
|
1315
|
+
counts << 0
|
1316
|
+
end
|
1317
|
+
end
|
1318
|
+
return counts
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
def self.increment_command_count(target_name, packet_name, count, scope:)
|
1322
|
+
result = Store.hincrby("#{scope}__COMMANDCNTS__{#{target_name}}", packet_name, count)
|
1323
|
+
if String === result
|
1324
|
+
return result.to_i
|
1325
|
+
else
|
1326
|
+
return result
|
1327
|
+
end
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
def self.get_all_command_counts(target_name, scope:)
|
1331
|
+
result = {}
|
1332
|
+
get_all = Store.hgetall("#{scope}__COMMANDCNTS__{#{target_name}}")
|
1333
|
+
if Hash === get_all
|
1334
|
+
get_all.each do |key, value|
|
1335
|
+
result[key] = value.to_i
|
1336
|
+
end
|
1337
|
+
else
|
1338
|
+
return result
|
1339
|
+
end
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
def self.get_command_count(target_name, packet_name, scope:)
|
1343
|
+
value = Store.hget("#{scope}__COMMANDCNTS__{#{target_name}}", packet_name)
|
1344
|
+
if String === value
|
1345
|
+
return value.to_i
|
1346
|
+
elsif value.nil?
|
1347
|
+
return 0 # Return 0 if the key doesn't exist
|
1348
|
+
else
|
1349
|
+
return value
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
def self.get_command_counts(target_packets, scope:)
|
1354
|
+
result = Store.redis_pool.pipelined do
|
1355
|
+
target_packets.each do |target_name, packet_name|
|
1356
|
+
target_name = target_name.upcase
|
1357
|
+
packet_name = packet_name.upcase
|
1358
|
+
Store.hget("#{scope}__COMMANDCNTS__{#{target_name}}", packet_name)
|
1359
|
+
end
|
1360
|
+
end
|
1361
|
+
counts = []
|
1362
|
+
result.each do |count|
|
1363
|
+
if count
|
1364
|
+
counts << count.to_i
|
1365
|
+
else
|
1366
|
+
counts << 0
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
return counts
|
1370
|
+
end
|
1236
1371
|
end
|
1237
1372
|
end
|
@@ -14,11 +14,14 @@
|
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
|
16
16
|
# Modified by OpenC3, Inc.
|
17
|
-
# All changes Copyright
|
17
|
+
# All changes Copyright 2025, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
|
+
#
|
23
|
+
# A portion of this file was funded by Blue Origin Enterprises, L.P.
|
24
|
+
# See https://github.com/OpenC3/cosmos/pull/1963
|
22
25
|
|
23
26
|
require 'openc3/packets/packet_config'
|
24
27
|
|
@@ -95,6 +98,9 @@ module OpenC3
|
|
95
98
|
# an uninitialized copy of the command. Thus you must use the return value
|
96
99
|
# of this method.
|
97
100
|
#
|
101
|
+
# Note: this method does not increment received_count and it should be
|
102
|
+
# incremented externally if needed.
|
103
|
+
#
|
98
104
|
# @param (see #identify_tlm!)
|
99
105
|
# @return (see #identify_tlm!)
|
100
106
|
def identify(packet_data, target_names = nil)
|
@@ -133,7 +139,6 @@ module OpenC3
|
|
133
139
|
end
|
134
140
|
|
135
141
|
if identified_packet
|
136
|
-
identified_packet.received_count += 1
|
137
142
|
identified_packet = identified_packet.clone
|
138
143
|
identified_packet.received_time = nil
|
139
144
|
identified_packet.stored = false
|
@@ -149,6 +154,9 @@ module OpenC3
|
|
149
154
|
# Returns a copy of the specified command packet with the parameters
|
150
155
|
# initialized to the given params values.
|
151
156
|
#
|
157
|
+
# Note: this method does not increment received_count and it should be
|
158
|
+
# incremented externally if needed.
|
159
|
+
#
|
152
160
|
# @param target_name (see #packet)
|
153
161
|
# @param packet_name (see #packet)
|
154
162
|
# @param params [Hash<param_name=>param_value>] Parameter items to override
|
@@ -164,7 +172,6 @@ module OpenC3
|
|
164
172
|
|
165
173
|
# Lookup the command and create a light weight copy
|
166
174
|
pkt = packet(target_upcase, packet_upcase)
|
167
|
-
pkt.received_count += 1
|
168
175
|
command = pkt.clone
|
169
176
|
|
170
177
|
# Restore the command's buffer to a zeroed string of defined length
|
@@ -187,7 +187,7 @@ module OpenC3
|
|
187
187
|
# build_cmd('TGT','CMD',{'PARAM1'=>val,'PARAM2'=>val})
|
188
188
|
def build_cmd(*args, range_check: true, raw: false, scope: $openc3_scope, **kwargs)
|
189
189
|
extract_string_kwargs_to_args(args, kwargs)
|
190
|
-
$api_server.
|
190
|
+
$api_server.build_cmd(*args)
|
191
191
|
end
|
192
192
|
# build_command is DEPRECATED
|
193
193
|
alias build_command build_cmd
|
data/lib/openc3/script/script.rb
CHANGED
@@ -216,6 +216,20 @@ module OpenC3
|
|
216
216
|
# NOOP
|
217
217
|
end
|
218
218
|
|
219
|
+
# Note: Enterprise Only - Use this for first time setup of an offline access token
|
220
|
+
# so that users can run scripts. Not necessary if accessing APIs via the web
|
221
|
+
# frontend as it handles it automatically.
|
222
|
+
#
|
223
|
+
# Example:
|
224
|
+
# initialize_offline_access()
|
225
|
+
# script_run("INST/procedures/collect.rb")
|
226
|
+
#
|
227
|
+
def initialize_offline_access
|
228
|
+
auth = OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
|
229
|
+
auth.token(include_bearer: true, openid_scope: 'openid%20offline_access')
|
230
|
+
set_offline_access(auth.refresh_token)
|
231
|
+
end
|
232
|
+
|
219
233
|
###########################################################################
|
220
234
|
# END PUBLIC API
|
221
235
|
###########################################################################
|
@@ -75,14 +75,14 @@ module OpenC3
|
|
75
75
|
end
|
76
76
|
|
77
77
|
# Load the token from the environment
|
78
|
-
def token(include_bearer: true)
|
78
|
+
def token(include_bearer: true, openid_scope: 'openid')
|
79
79
|
@auth_mutex.synchronize do
|
80
80
|
@log = [nil, nil]
|
81
81
|
current_time = Time.now.to_i
|
82
82
|
if @token.nil?
|
83
|
-
_make_token(current_time)
|
83
|
+
_make_token(current_time, openid_scope: openid_scope)
|
84
84
|
elsif @refresh_expires_at < current_time
|
85
|
-
_make_token(current_time)
|
85
|
+
_make_token(current_time, openid_scope: openid_scope)
|
86
86
|
elsif @expires_at < current_time
|
87
87
|
_refresh_token(current_time)
|
88
88
|
end
|
@@ -108,13 +108,13 @@ module OpenC3
|
|
108
108
|
private
|
109
109
|
|
110
110
|
# Make the token and save token to instance
|
111
|
-
def _make_token(current_time)
|
111
|
+
def _make_token(current_time, openid_scope: 'openid')
|
112
112
|
client_id = ENV['OPENC3_API_CLIENT'] || 'api'
|
113
113
|
if ENV['OPENC3_API_USER'] and ENV['OPENC3_API_PASSWORD']
|
114
114
|
# Username and password
|
115
115
|
data = "username=#{ENV['OPENC3_API_USER']}&password=#{ENV['OPENC3_API_PASSWORD']}"
|
116
116
|
data << "&client_id=#{client_id}"
|
117
|
-
data <<
|
117
|
+
data << "&grant_type=password&scope=#{openid_scope}"
|
118
118
|
headers = {
|
119
119
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
120
120
|
'User-Agent' => "OpenC3KeycloakAuthorization / #{OPENC3_VERSION} (ruby/openc3/lib/utilities/authentication)",
|
@@ -402,17 +402,20 @@ module OpenC3
|
|
402
402
|
nil
|
403
403
|
end
|
404
404
|
|
405
|
-
def self.local_target_files(scope:, path_matchers:, include_temp: false)
|
405
|
+
def self.local_target_files(scope:, path_matchers:, include_temp: false, target: nil)
|
406
406
|
files = []
|
407
407
|
local_catalog = build_local_catalog(scope: scope)
|
408
408
|
local_catalog.each do |key, _size|
|
409
409
|
split_key = key.split('/')
|
410
410
|
# DEFAULT/targets_modified/__TEMP__/YYYY_MM_DD_HH_MM_SS_mmm_temp.rb
|
411
411
|
# See target_file.rb TEMP_FOLDER
|
412
|
-
if split_key[2]
|
412
|
+
if split_key[2] == '__TEMP__'
|
413
413
|
files << split_key[2..-1].join('/') if include_temp
|
414
414
|
next
|
415
415
|
end
|
416
|
+
if target and split_key[2] != target
|
417
|
+
next
|
418
|
+
end
|
416
419
|
if path_matchers
|
417
420
|
found = false
|
418
421
|
path_matchers.each do |path|
|