openc3 5.9.0 → 5.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35a757bea46ad5b21ac348aa90b6163595d04c702b752b728f628d7dd82f6132
4
- data.tar.gz: 149be2f48cdb7864a3665bf46982a1e16fb37541f68203387297c14d0d55e7d8
3
+ metadata.gz: c24a28732bfd8c275b65639cd566b4823d78b70b43781bf14a82e114397e7677
4
+ data.tar.gz: a8567e57ec8008d505e7f6a30cd3462e435988a46729908fbc17775f2b92e976
5
5
  SHA512:
6
- metadata.gz: 1595ea101a9473b1aa5a1894d08bcd5704943fae771f9e58c4506b905cae9cb5957ab0fc961349ec9ecc4466a3ae34b6987a2a9d21ed8b9d2b0d31592c16908b
7
- data.tar.gz: afbf06dfc4d4dbbfd7916146b6ee2b3005959bae650735127710b257a675487591adb5739744e603ef5eb20cc545b4dc79962021aaef00127c9aa76017a4ba3e
6
+ metadata.gz: dc287a9895e3ae60c6332389d0b7926bb84aa04d7d65e51c936ba65f66640b179b0baefdcec232d7775a70f2e95d88557faf5e960663c4c28840428e7cb52904
7
+ data.tar.gz: 578c1f1fe45512344390492e9aa15d7af851bb81e608fbc874189dcc73bc998c472e62d449c114fb300fc58bb0d9cc269cc3dfbcbb31aaaf76c9003abd63c8e7
@@ -373,26 +373,10 @@ module OpenC3
373
373
  # @param item_name [String] item name
374
374
  # @param scope [String] scope
375
375
  # @return Hash The requested item based on the packet name
376
- def _get_item(target_name, packet_name, item_name, scope:)
377
- requested_item = nil
378
- if packet_name == 'LATEST'
379
- latest = -1
380
- TargetModel.packets(target_name, scope: scope).each do |packet|
381
- item = packet['items'].find { |item| item['name'] == item_name }
382
- if item
383
- hash = CvtModel.get(target_name: target_name, packet_name: packet['packet_name'], scope: scope)
384
- if hash['PACKET_TIMESECONDS'] && hash['PACKET_TIMESECONDS'] > latest
385
- latest = hash['PACKET_TIMESECONDS']
386
- requested_item = item
387
- end
388
- end
389
- end
390
- raise "Item '#{target_name} LATEST #{item_name}' does not exist" if latest == -1
391
- else
392
- # Determine if this item exists, it will raise appropriate errors if not
393
- requested_item = TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
394
- end
395
- return requested_item
376
+ def _get_item(target_name, packet_name, item_name, cache_timeout: 0.1, scope:)
377
+ # Determine if this item exists, it will raise appropriate errors if not
378
+ packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: $openc3_scope) if packet_name == 'LATEST'
379
+ return TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
396
380
  end
397
381
  end
398
382
  end
@@ -26,19 +26,22 @@ module OpenC3
26
26
  module Api
27
27
  WHITELIST ||= []
28
28
  WHITELIST.concat([
29
- 'get_target_list',
30
- 'get_target',
31
- 'get_target_interfaces',
32
- 'get_all_target_info', # DEPRECATED
33
- ])
29
+ 'get_target_names',
30
+ 'get_target_list', # DEPRECATED
31
+ 'get_target',
32
+ 'get_target_interfaces',
33
+ 'get_all_target_info', # DEPRECATED
34
+ ])
34
35
 
35
36
  # Returns the list of all target names
36
37
  #
37
38
  # @return [Array<String>] All target names
38
- def get_target_list(scope: $openc3_scope, token: $openc3_token)
39
+ def get_target_names(scope: $openc3_scope, token: $openc3_token)
39
40
  authorize(permission: 'tlm', scope: scope, token: token)
40
41
  TargetModel.names(scope: scope)
41
42
  end
43
+ # get_target_list is DEPRECATED
44
+ alias get_target_list get_target_names
42
45
 
43
46
  # Gets the full target hash
44
47
  #
@@ -57,7 +60,7 @@ module OpenC3
57
60
  authorize(permission: 'system', scope: scope, token: token)
58
61
  info = []
59
62
  interfaces = InterfaceModel.all(scope: scope)
60
- get_target_list(scope: scope, token: token).each do |target_name|
63
+ get_target_names(scope: scope, token: token).each do |target_name|
61
64
  interface_names = []
62
65
  interfaces.each do |name, interface|
63
66
  if interface['target_names'].include? target_name
@@ -76,7 +79,7 @@ module OpenC3
76
79
  def get_all_target_info(scope: $openc3_scope, token: $openc3_token)
77
80
  authorize(permission: 'system', scope: scope, token: token)
78
81
  info = []
79
- get_target_list(scope: scope, token: token).each do |target_name|
82
+ get_target_names(scope: scope, token: token).each do |target_name|
80
83
  cmd_cnt = 0
81
84
  packets = TargetModel.packets(target_name, type: :CMD, scope: scope)
82
85
  packets.each do |packet|
@@ -66,30 +66,30 @@ module OpenC3
66
66
  # @param args [String|Array<String>] See the description for calling style
67
67
  # @param type [Symbol] Telemetry type, :RAW, :CONVERTED (default), :FORMATTED, or :WITH_UNITS
68
68
  # @return [Object] The telemetry value formatted as requested
69
- def tlm(*args, type: :CONVERTED, scope: $openc3_scope, token: $openc3_token)
70
- target_name, packet_name, item_name = tlm_process_args(args, 'tlm', scope: scope)
69
+ def tlm(*args, type: :CONVERTED, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
70
+ target_name, packet_name, item_name = tlm_process_args(args, 'tlm', cache_timeout: cache_timeout, scope: scope)
71
71
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
72
- CvtModel.get_item(target_name, packet_name, item_name, type: type.intern, scope: scope)
72
+ CvtModel.get_item(target_name, packet_name, item_name, type: type.intern, cache_timeout: cache_timeout, scope: scope)
73
73
  end
74
74
 
75
75
  # @deprecated Use tlm with type: :RAW
76
- def tlm_raw(*args, scope: $openc3_scope, token: $openc3_token)
77
- tlm(*args, type: :RAW, scope: scope, token: token)
76
+ def tlm_raw(*args, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
77
+ tlm(*args, type: :RAW, cache_timeout: cache_timeout, scope: scope, token: token)
78
78
  end
79
79
 
80
80
  # @deprecated Use tlm with type: :FORMATTED
81
- def tlm_formatted(*args, scope: $openc3_scope, token: $openc3_token)
82
- tlm(*args, type: :FORMATTED, scope: scope, token: token)
81
+ def tlm_formatted(*args, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
82
+ tlm(*args, type: :FORMATTED, cache_timeout: cache_timeout, scope: scope, token: token)
83
83
  end
84
84
 
85
85
  # @deprecated Use tlm with type: :WITH_UNITS
86
- def tlm_with_units(*args, scope: $openc3_scope, token: $openc3_token)
87
- tlm(*args, type: :WITH_UNITS, scope: scope, token: token)
86
+ def tlm_with_units(*args, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
87
+ tlm(*args, type: :WITH_UNITS, cache_timeout: cache_timeout, scope: scope, token: token)
88
88
  end
89
89
 
90
90
  # @deprecated Use tlm with type:
91
- def tlm_variable(*args, scope: $openc3_scope, token: $openc3_token)
92
- tlm(*args[0..-2], type: args[-1].intern, scope: scope, token: token)
91
+ def tlm_variable(*args, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
92
+ tlm(*args[0..-2], type: args[-1].intern, cache_timeout: cache_timeout, scope: scope, token: token)
93
93
  end
94
94
 
95
95
  # Set a telemetry item in the current value table.
@@ -227,7 +227,7 @@ module OpenC3
227
227
  # @return [Array<String, Object, Symbol|nil>] Returns an Array consisting
228
228
  # of [item name, item value, item limits state] where the item limits
229
229
  # state can be one of {OpenC3::Limits::LIMITS_STATES}
230
- def get_tlm_packet(target_name, packet_name, stale_time: 30, type: :CONVERTED, scope: $openc3_scope, token: $openc3_token)
230
+ def get_tlm_packet(target_name, packet_name, stale_time: 30, type: :CONVERTED, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
231
231
  target_name = target_name.upcase
232
232
  packet_name = packet_name.upcase
233
233
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
@@ -235,8 +235,8 @@ module OpenC3
235
235
  t = _validate_tlm_type(type)
236
236
  raise ArgumentError, "Unknown type '#{type}' for #{target_name} #{packet_name}" if t.nil?
237
237
  items = packet['items'].map { | item | item['name'].upcase }
238
- cvt_items = items.map { | item | "#{target_name}__#{packet_name}__#{item}__#{type}" }
239
- current_values = CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, scope: scope)
238
+ cvt_items = items.map { | item | [target_name, packet_name, item, type] }
239
+ current_values = CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, cache_timeout: cache_timeout, scope: scope)
240
240
  items.zip(current_values).map { | item , values | [item, values[0], values[1]]}
241
241
  end
242
242
 
@@ -250,25 +250,26 @@ module OpenC3
250
250
  # @return [Array<Object, Symbol>]
251
251
  # Array consisting of the item value and limits state
252
252
  # given as symbols such as :RED, :YELLOW, :STALE
253
- def get_tlm_values(items, stale_time: 30, scope: $openc3_scope, token: $openc3_token)
253
+ def get_tlm_values(items, stale_time: 30, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
254
254
  if !items.is_a?(Array) || !items[0].is_a?(String)
255
255
  raise ArgumentError, "items must be array of strings: ['TGT__PKT__ITEM__TYPE', ...]"
256
256
  end
257
+ packets = []
258
+ cvt_items = []
257
259
  items.each_with_index do |item, index|
258
- target_name, packet_name, item_name, value_type = item.split('__')
260
+ item_upcase = item.to_s.upcase
261
+ target_name, packet_name, item_name, value_type = item_upcase.split('__')
259
262
  raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
260
- target_name = target_name.upcase
261
- packet_name = packet_name.upcase
262
- item_name = item_name.upcase
263
- value_type = value_type.upcase
264
- if packet_name == 'LATEST'
265
- _, packet_name, _ = tlm_process_args([target_name, packet_name, item_name], 'get_tlm_values', scope: scope) # Figure out which packet is LATEST
266
- end
263
+ packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope) if packet_name == 'LATEST'
267
264
  # Change packet_name in case of LATEST and ensure upcase
268
- items[index] = "#{target_name}__#{packet_name}__#{item_name}__#{value_type}"
265
+ cvt_items[index] = [target_name, packet_name, item_name, value_type]
266
+ packets << [target_name, packet_name]
267
+ end
268
+ packets.uniq!
269
+ packets.each do |target_name, packet_name|
269
270
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
270
271
  end
271
- CvtModel.get_tlm_values(items, stale_time: stale_time, scope: scope)
272
+ CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, cache_timeout: cache_timeout, scope: scope)
272
273
  end
273
274
 
274
275
  # Returns an array of all the telemetry packet hashes
@@ -429,7 +430,7 @@ module OpenC3
429
430
  return nil
430
431
  end
431
432
 
432
- def tlm_process_args(args, method_name, scope: $openc3_scope, token: $openc3_token)
433
+ def tlm_process_args(args, method_name, cache_timeout: 0.1, scope: $openc3_scope, token: $openc3_token)
433
434
  case args.length
434
435
  when 1
435
436
  target_name, packet_name, item_name = extract_fields_from_tlm_text(args[0])
@@ -444,19 +445,9 @@ module OpenC3
444
445
  target_name = target_name.upcase
445
446
  packet_name = packet_name.upcase
446
447
  item_name = item_name.upcase
448
+
447
449
  if packet_name == 'LATEST'
448
- latest = -1
449
- TargetModel.packets(target_name, scope: scope).each do |packet|
450
- item = packet['items'].find { |item| item['name'] == item_name }
451
- if item
452
- hash = CvtModel.get(target_name: target_name, packet_name: packet['packet_name'], scope: scope)
453
- if hash['PACKET_TIMESECONDS'] && hash['PACKET_TIMESECONDS'] > latest
454
- latest = hash['PACKET_TIMESECONDS']
455
- packet_name = packet['packet_name']
456
- end
457
- end
458
- end
459
- raise "Item '#{target_name} LATEST #{item_name}' does not exist" if latest == -1
450
+ packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope)
460
451
  else
461
452
  # Determine if this item exists, it will raise appropriate errors if not
462
453
  TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
@@ -17,7 +17,7 @@
17
17
  # All changes Copyright 2022, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
- # This file may also be used under the terms of a commercial license
20
+ # This file may also be used under the terms of a commercial license
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'digest'
@@ -28,6 +28,12 @@ module OpenC3
28
28
  PRIMARY_KEY = 'OPENC3__TOKEN'
29
29
  SERVICE_KEY = 'OPENC3__SERVICE__TOKEN'
30
30
 
31
+ TOKEN_CACHE_TIMEOUT = 5
32
+ @@token_cache = nil
33
+ @@token_cache_time = nil
34
+ @@service_token_cache = nil
35
+ @@service_token_cache_time = nil
36
+
31
37
  def self.is_set?(key = PRIMARY_KEY)
32
38
  Store.exists(key) == 1
33
39
  end
@@ -36,15 +42,21 @@ module OpenC3
36
42
  return false if token.nil? or token.empty?
37
43
 
38
44
  token_hash = hash(token)
39
- return true if Store.get(PRIMARY_KEY) == token_hash
45
+ return true if @@token_cache and (Time.now - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
46
+ return true if @@service_token_cache and (Time.now - @@service_token_cache_time) < TOKEN_CACHE_TIMEOUT and @@service_token_cache == token_hash and permission != 'admin'
47
+
48
+ @@token_cache = Store.get(PRIMARY_KEY)
49
+ @@token_cache_time = Time.now
50
+ return true if @@token_cache == token_hash
40
51
 
41
- service_hash = Store.get(SERVICE_KEY)
42
- if ENV['OPENC3_SERVICE_PASSWORD'] and hash(ENV['OPENC3_SERVICE_PASSWORD']) != service_hash
52
+ @@service_token_cache = Store.get(SERVICE_KEY)
53
+ @@service_token_cache_time = @@token_cache_time
54
+ if ENV['OPENC3_SERVICE_PASSWORD'] and hash(ENV['OPENC3_SERVICE_PASSWORD']) != @@service_token_cache
43
55
  set_hash = hash(ENV['OPENC3_SERVICE_PASSWORD'])
44
56
  OpenC3::Store.set(SERVICE_KEY, set_hash)
45
- service_hash = set_hash
57
+ @@service_token_cache = set_hash
46
58
  end
47
- return true if service_hash == token_hash and permission != 'admin'
59
+ return true if @@service_token_cache == token_hash and permission != 'admin'
48
60
  return false
49
61
  end
50
62
 
@@ -25,6 +25,9 @@ require 'openc3/models/target_model'
25
25
 
26
26
  module OpenC3
27
27
  class CvtModel
28
+ @@packet_cache = {}
29
+ @@override_cache = {}
30
+
28
31
  VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
29
32
  def self.build_json_from_packet(packet)
30
33
  packet.decom
@@ -32,24 +35,39 @@ module OpenC3
32
35
 
33
36
  # Delete the current value table for a target
34
37
  def self.del(target_name:, packet_name:, scope: $openc3_scope)
35
- Store.hdel("#{scope}__tlm__#{target_name}", packet_name)
38
+ key = "#{scope}__tlm__#{target_name}"
39
+ tgt_pkt_key = key + "__#{packet_name}"
40
+ @@packet_cache[tgt_pkt_key] = nil
41
+ Store.hdel(key, packet_name)
36
42
  end
37
43
 
38
44
  # Set the current value table for a target, packet
39
45
  def self.set(hash, target_name:, packet_name:, scope: $openc3_scope)
40
- Store.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
46
+ packet_json = JSON.generate(hash.as_json(:allow_nan => true))
47
+ key = "#{scope}__tlm__#{target_name}"
48
+ tgt_pkt_key = key + "__#{packet_name}"
49
+ @@packet_cache[tgt_pkt_key] = [Time.now, hash]
50
+ Store.hset(key, packet_name, packet_json)
41
51
  end
42
52
 
43
53
  # Get the hash for packet in the CVT
44
- def self.get(target_name:, packet_name:, scope: $openc3_scope)
45
- packet = Store.hget("#{scope}__tlm__#{target_name}", packet_name)
54
+ # Note: Does not apply overrides
55
+ def self.get(target_name:, packet_name:, cache_timeout: 0.1, scope: $openc3_scope)
56
+ key = "#{scope}__tlm__#{target_name}"
57
+ tgt_pkt_key = key + "__#{packet_name}"
58
+ cache_time, hash = @@packet_cache[tgt_pkt_key]
59
+ now = Time.now
60
+ return hash if hash and (now - cache_time) < cache_timeout
61
+ packet = Store.hget(key, packet_name)
46
62
  raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
47
- JSON.parse(packet, :allow_nan => true, :create_additions => true)
63
+ hash = JSON.parse(packet, :allow_nan => true, :create_additions => true)
64
+ @@packet_cache[tgt_pkt_key] = [now, hash]
65
+ hash
48
66
  end
49
67
 
50
68
  # Set an item in the current value table
51
69
  def self.set_item(target_name, packet_name, item_name, value, type:, scope: $openc3_scope)
52
- hash = get(target_name: target_name, packet_name: packet_name, scope: scope)
70
+ hash = get(target_name: target_name, packet_name: packet_name, cache_timeout: 0.0, scope: scope)
53
71
  case type
54
72
  when :WITH_UNITS
55
73
  hash["#{item_name}__U"] = value.to_s # WITH_UNITS should always be a string
@@ -67,33 +85,13 @@ module OpenC3
67
85
  else
68
86
  raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
69
87
  end
70
- Store.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
88
+ set(hash, target_name: target_name, packet_name: packet_name, scope: scope)
71
89
  end
72
90
 
73
91
  # Get an item from the current value table
74
- def self.get_item(target_name, packet_name, item_name, type:, scope: $openc3_scope)
75
- override_key = item_name
76
- types = []
77
- case type
78
- when :WITH_UNITS
79
- types = ["#{item_name}__U", "#{item_name}__F", "#{item_name}__C", item_name]
80
- override_key = "#{item_name}__U"
81
- when :FORMATTED
82
- types = ["#{item_name}__F", "#{item_name}__C", item_name]
83
- override_key = "#{item_name}__F"
84
- when :CONVERTED
85
- types = ["#{item_name}__C", item_name]
86
- override_key = "#{item_name}__C"
87
- when :RAW
88
- types = [item_name]
89
- else
90
- raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
91
- end
92
- overrides = Store.hget("#{scope}__override__#{target_name}", packet_name)
93
- if overrides
94
- result = JSON.parse(overrides, :allow_nan => true, :create_additions => true)[override_key]
95
- return result if result
96
- end
92
+ def self.get_item(target_name, packet_name, item_name, type:, cache_timeout: 0.1, scope: $openc3_scope)
93
+ result, types = self._handle_item_override(target_name, packet_name, item_name, type: type, cache_timeout: cache_timeout, scope: scope)
94
+ return result if result
97
95
  hash = get(target_name: target_name, packet_name: packet_name, scope: scope)
98
96
  hash.values_at(*types).each do |result|
99
97
  if result
@@ -111,18 +109,19 @@ module OpenC3
111
109
  # @param items [Array<String>] Items to return. Must be formatted as TGT__PKT__ITEM__TYPE
112
110
  # @param stale_time [Integer] Time in seconds from Time.now that value will be marked stale
113
111
  # @return [Array] Array of values
114
- def self.get_tlm_values(items, stale_time: 30, scope: $openc3_scope)
115
- now = Time.now.sys.to_f
112
+ def self.get_tlm_values(items, stale_time: 30, cache_timeout: 0.1, scope: $openc3_scope)
113
+ now = Time.now
116
114
  results = []
117
115
  lookups = []
118
116
  packet_lookup = {}
119
117
  overrides = {}
120
118
  # First generate a lookup hash of all the items represented so we can query the CVT
121
- items.each { |item| _parse_item(lookups, overrides, item, scope: scope) }
119
+ items.each { |item| _parse_item(now, lookups, overrides, item, cache_timeout: cache_timeout, scope: scope) }
122
120
 
121
+ now = now.to_f
123
122
  lookups.each do |target_packet_key, target_name, packet_name, value_keys|
124
123
  unless packet_lookup[target_packet_key]
125
- packet_lookup[target_packet_key] = get(target_name: target_name, packet_name: packet_name, scope: scope)
124
+ packet_lookup[target_packet_key] = get(target_name: target_name, packet_name: packet_name, cache_timeout: cache_timeout, scope: scope)
126
125
  end
127
126
  hash = packet_lookup[target_packet_key]
128
127
  item_result = []
@@ -153,6 +152,7 @@ module OpenC3
153
152
  end
154
153
 
155
154
  # Return all the overrides
155
+ # Note: Does not use cache to benefit from hgetall
156
156
  def self.overrides(scope: $openc3_scope)
157
157
  overrides = []
158
158
  TargetModel.names(scope: scope).each do |target_name|
@@ -207,6 +207,9 @@ module OpenC3
207
207
  else
208
208
  raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
209
209
  end
210
+
211
+ tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
212
+ @@override_cache[tgt_pkt_key] = [Time.now, hash]
210
213
  Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
211
214
  end
212
215
 
@@ -232,6 +235,9 @@ module OpenC3
232
235
  else
233
236
  raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
234
237
  end
238
+
239
+ tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
240
+ @@override_cache[tgt_pkt_key] = [Time.now, hash]
235
241
  if hash.empty?
236
242
  Store.hdel("#{scope}__override__#{target_name}", packet_name)
237
243
  else
@@ -239,16 +245,78 @@ module OpenC3
239
245
  end
240
246
  end
241
247
 
248
+ def self.determine_latest_packet_for_item(target_name, item_name, cache_timeout: 0.1, scope: $openc3_scope)
249
+ item_map = TargetModel.get_item_to_packet_map(target_name, scope: scope)
250
+ packet_names = item_map[item_name]
251
+ raise "Item '#{target_name} LATEST #{item_name}' does not exist for scope: #{scope}" unless packet_names
252
+
253
+ latest = -1
254
+ latest_packet_name = nil
255
+ packet_names.each do |packet_name|
256
+ hash = get(target_name: target_name, packet_name: packet_name, cache_timeout: cache_timeout, scope: scope)
257
+ if hash['PACKET_TIMESECONDS'] && hash['PACKET_TIMESECONDS'] > latest
258
+ latest = hash['PACKET_TIMESECONDS']
259
+ latest_packet_name = packet_name
260
+ end
261
+ end
262
+ raise "Item '#{target_name} LATEST #{item_name}' does not exist for scope: #{scope}" if latest == -1
263
+ return latest_packet_name
264
+ end
265
+
242
266
  # PRIVATE METHODS
243
267
 
268
+ def self._handle_item_override(target_name, packet_name, item_name, type:, cache_timeout:, scope: $openc3_scope)
269
+ override_key = item_name
270
+ types = []
271
+ case type
272
+ when :WITH_UNITS
273
+ types = ["#{item_name}__U", "#{item_name}__F", "#{item_name}__C", item_name]
274
+ override_key = "#{item_name}__U"
275
+ when :FORMATTED
276
+ types = ["#{item_name}__F", "#{item_name}__C", item_name]
277
+ override_key = "#{item_name}__F"
278
+ when :CONVERTED
279
+ types = ["#{item_name}__C", item_name]
280
+ override_key = "#{item_name}__C"
281
+ when :RAW
282
+ types = [item_name]
283
+ else
284
+ raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
285
+ end
286
+
287
+ tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
288
+ overrides = _get_overrides(Time.now, tgt_pkt_key, {}, target_name, packet_name, cache_timeout: cache_timeout, scope: scope)
289
+ result = overrides[override_key]
290
+ return result, types if result
291
+ return nil, types
292
+ end
293
+
294
+ def self._get_overrides(now, tgt_pkt_key, overrides, target_name, packet_name, cache_timeout:, scope:)
295
+ cache_time, hash = @@override_cache[tgt_pkt_key]
296
+ if hash and (now - cache_time) < cache_timeout
297
+ overrides[tgt_pkt_key] = hash
298
+ return hash
299
+ end
300
+ override_data = Store.hget("#{scope}__override__#{target_name}", packet_name)
301
+ if override_data
302
+ hash = JSON.parse(override_data, :allow_nan => true, :create_additions => true)
303
+ overrides[tgt_pkt_key] = hash
304
+ else
305
+ hash = {}
306
+ overrides[tgt_pkt_key] = {}
307
+ end
308
+ @@override_cache[tgt_pkt_key] = [now, hash] # always update
309
+ return hash
310
+ end
311
+
244
312
  # parse item and update lookups with packet_name and target_name and keys
245
313
  # return an ordered array of hash with keys
246
- def self._parse_item(lookups, overrides, item, scope:)
247
- target_name, packet_name, item_name, value_type = item.split('__')
314
+ def self._parse_item(now, lookups, overrides, item, cache_timeout:, scope:)
315
+ target_name, packet_name, item_name, value_type = item
248
316
 
249
317
  # We build lookup keys by including all the less formatted types to gracefully degrade lookups
250
318
  # This allows the user to specify WITH_UNITS and if there is no conversions it will simply return the RAW value
251
- case value_type
319
+ case value_type.to_s
252
320
  when 'RAW'
253
321
  keys = [item_name]
254
322
  when 'CONVERTED'
@@ -260,20 +328,15 @@ module OpenC3
260
328
  else
261
329
  raise "Unknown value type '#{value_type}'"
262
330
  end
263
- tgt_pkt_key = "#{target_name}__#{packet_name}"
331
+
264
332
  # Check the overrides cache for this target / packet
265
- unless overrides[tgt_pkt_key]
266
- override_data = Store.hget("#{scope}__override__#{target_name}", packet_name)
267
- if override_data
268
- overrides[tgt_pkt_key] = JSON.parse(override_data, :allow_nan => true, :create_additions => true)
269
- else
270
- overrides[tgt_pkt_key] = {}
271
- end
272
- end
273
- if overrides[tgt_pkt_key][keys[0]]
274
- # Set the result as a Hash to distingish it from the key array and from an overridden Array value
275
- keys = {'value' => overrides[tgt_pkt_key][keys[0]]}
276
- end
333
+ tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
334
+ _get_overrides(now, tgt_pkt_key, overrides, target_name, packet_name, cache_timeout: cache_timeout, scope: scope) unless overrides[tgt_pkt_key]
335
+
336
+ # Set the result as a Hash to distinguish it from the key array and from an overridden Array value
337
+ value = overrides[tgt_pkt_key][keys[0]]
338
+ keys = {'value' => value} if value
339
+
277
340
  lookups << [tgt_pkt_key, target_name, packet_name, keys]
278
341
  end
279
342
  end
@@ -44,6 +44,8 @@ module OpenC3
44
44
  class TargetModel < Model
45
45
  PRIMARY_KEY = 'openc3_targets'
46
46
  VALID_TYPES = %i(CMD TLM)
47
+ ITEM_MAP_CACHE_TIMEOUT = 10.0
48
+ @@item_map_cache = {}
47
49
 
48
50
  attr_accessor :folder_name
49
51
  attr_accessor :requires
@@ -208,7 +210,7 @@ module OpenC3
208
210
  # @return [Array>Hash>] All packet hashes under the target_name
209
211
  def self.packets(target_name, type: :TLM, scope:)
210
212
  raise "Unknown type #{type} for #{target_name}" unless VALID_TYPES.include?(type)
211
- raise "Target '#{target_name}' does not exist" unless get(name: target_name, scope: scope)
213
+ raise "Target '#{target_name}' does not exist for scope: #{scope}" unless get(name: target_name, scope: scope)
212
214
 
213
215
  result = []
214
216
  packets = Store.hgetall("#{scope}__openc3#{type.to_s.downcase}__#{target_name}")
@@ -268,6 +270,36 @@ module OpenC3
268
270
  end
269
271
  end
270
272
 
273
+ def self.get_item_to_packet_map(target_name, scope:)
274
+ cache_time, item_map = @@item_map_cache[target_name]
275
+ return item_map if item_map and (Time.now - cache_time) < ITEM_MAP_CACHE_TIMEOUT
276
+ item_map_key = "#{scope}__#{target_name}__item_to_packet_map"
277
+ target_name = target_name.upcase
278
+ json_data = Store.get(item_map_key)
279
+ if json_data
280
+ item_map = JSON.parse(json_data, :allow_nan => true, :create_additions => true)
281
+ else
282
+ item_map = build_item_to_packet_map(target_name, scope: scope)
283
+ Store.set(item_map_key, JSON.generate(item_map, :allow_nan => true))
284
+ end
285
+ @@item_map_cache[target_name] = [Time.now, item_map]
286
+ return item_map
287
+ end
288
+
289
+ def self.build_item_to_packet_map(target_name, scope:)
290
+ item_map = {}
291
+ packets = packets(target_name, scope: scope)
292
+ packets.each do |packet|
293
+ items = packet['items']
294
+ items.each do |item|
295
+ item_name = item['name']
296
+ item_map[item_name] ||= []
297
+ item_map[item_name] << packet['packet_name']
298
+ end
299
+ end
300
+ return item_map
301
+ end
302
+
271
303
  # Called by the PluginModel to allow this class to validate it's top-level keyword: "TARGET"
272
304
  def self.handle_config(parser, keyword, parameters, plugin: nil, needs_dependencies: false, scope:)
273
305
  case keyword
@@ -601,6 +633,10 @@ module OpenC3
601
633
  model.destroy if model
602
634
  end
603
635
  end
636
+ # Delete item_map
637
+ item_map_key = "#{@scope}__#{@name}__item_to_packet_map"
638
+ Store.del(item_map_key)
639
+ @@item_map_cache[@name] = nil
604
640
 
605
641
  ConfigTopic.write({ kind: 'deleted', type: 'target', name: @name, plugin: @plugin }, scope: @scope)
606
642
  end
@@ -731,6 +767,12 @@ module OpenC3
731
767
  end
732
768
  Store.hmset("#{@scope}__limits_sets", *sets)
733
769
 
770
+ # Create item_map
771
+ item_map_key = "#{@scope}__#{@name}__item_to_packet_map"
772
+ item_map = self.class.build_item_to_packet_map(@name, scope: @scope)
773
+ Store.set(item_map_key, JSON.generate(item_map, :allow_nan => true))
774
+ @@item_map_cache[@name] = [Time.now, item_map]
775
+
734
776
  return system
735
777
  end
736
778
 
@@ -431,7 +431,7 @@ module OpenC3
431
431
  # Parse just to ensure we have valid JSON
432
432
  JSON.parse(data, :allow_nan => true, :create_additions => true)
433
433
  # Only save if the parse was successful
434
- ToolConfigModel.save_config(parts[-2], File.basename(config), data, scope: scope, local_mode: false)
434
+ ToolConfigModel.save_config(parts[-2], File.basename(config, '.json'), data, scope: scope, local_mode: false)
435
435
  rescue JSON::ParserError => error
436
436
  puts "Unable to initialize tool config due to #{error.message}"
437
437
  end
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- OPENC3_VERSION = '5.9.0'
3
+ OPENC3_VERSION = '5.9.1'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '5'
7
7
  MINOR = '9'
8
- PATCH = '0'
8
+ PATCH = '1'
9
9
  OTHER = ''
10
- BUILD = '7ad48e4189eb35c8a08051cc9856d0bd8be27f60'
10
+ BUILD = 'e60c6e1f7e80f87eae7bc623986940da5449d6ad'
11
11
  end
12
- VERSION = '5.9.0'
13
- GEM_VERSION = '5.9.0'
12
+ VERSION = '5.9.1'
13
+ GEM_VERSION = '5.9.1'
14
14
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= tool_name %>",
3
- "version": "5.9.0",
3
+ "version": "5.9.1",
4
4
  "scripts": {
5
5
  "ng": "ng",
6
6
  "start": "ng serve",
@@ -22,7 +22,7 @@
22
22
  "@angular/platform-browser": "^16.1.3",
23
23
  "@angular/platform-browser-dynamic": "^16.1.3",
24
24
  "@angular/router": "^16.1.3",
25
- "@openc3/tool-common": "5.9.0",
25
+ "@openc3/tool-common": "5.9.1",
26
26
  "rxjs": "~7.8.0",
27
27
  "single-spa": ">=5.9.5",
28
28
  "single-spa-angular": "^8.1.0",
@@ -45,7 +45,7 @@
45
45
  "@emotion/react": "^11.11.1",
46
46
  "@emotion/styled": "^11.11.0",
47
47
  "@mui/material": "^5.13.6",
48
- "@openc3/tool-common": "5.9.0",
48
+ "@openc3/tool-common": "5.9.1",
49
49
  "react": "^18.2.0",
50
50
  "react-dom": "^18.2.0",
51
51
  "single-spa-react": "^5.1.1"