openc3 6.9.1 → 6.10.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +289 -0
  3. data/data/config/command_modifiers.yaml +79 -0
  4. data/data/config/item_modifiers.yaml +5 -0
  5. data/data/config/parameter_modifiers.yaml +5 -0
  6. data/data/config/telemetry_modifiers.yaml +79 -0
  7. data/ext/openc3/ext/packet/packet.c +9 -0
  8. data/lib/openc3/accessors/accessor.rb +27 -3
  9. data/lib/openc3/accessors/binary_accessor.rb +21 -4
  10. data/lib/openc3/accessors/template_accessor.rb +3 -2
  11. data/lib/openc3/api/cmd_api.rb +7 -3
  12. data/lib/openc3/api/tlm_api.rb +17 -7
  13. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +19 -13
  14. data/lib/openc3/interfaces.rb +6 -4
  15. data/lib/openc3/io/json_rpc.rb +6 -0
  16. data/lib/openc3/microservices/decom_microservice.rb +97 -17
  17. data/lib/openc3/microservices/interface_decom_common.rb +32 -0
  18. data/lib/openc3/microservices/interface_microservice.rb +12 -80
  19. data/lib/openc3/microservices/queue_microservice.rb +30 -7
  20. data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +23 -0
  21. data/lib/openc3/models/plugin_model.rb +69 -6
  22. data/lib/openc3/models/queue_model.rb +32 -5
  23. data/lib/openc3/models/reaction_model.rb +26 -10
  24. data/lib/openc3/models/target_model.rb +85 -13
  25. data/lib/openc3/models/trigger_model.rb +1 -1
  26. data/lib/openc3/packets/commands.rb +33 -7
  27. data/lib/openc3/packets/packet.rb +75 -71
  28. data/lib/openc3/packets/packet_config.rb +78 -29
  29. data/lib/openc3/packets/packet_item.rb +11 -103
  30. data/lib/openc3/packets/parsers/packet_item_parser.rb +177 -34
  31. data/lib/openc3/packets/parsers/xtce_converter.rb +2 -2
  32. data/lib/openc3/packets/structure.rb +29 -21
  33. data/lib/openc3/packets/structure_item.rb +31 -19
  34. data/lib/openc3/packets/telemetry.rb +37 -11
  35. data/lib/openc3/script/script.rb +1 -1
  36. data/lib/openc3/script/suite_results.rb +2 -2
  37. data/lib/openc3/subpacketizers/subpacketizer.rb +18 -0
  38. data/lib/openc3/system/system.rb +3 -3
  39. data/lib/openc3/system/target.rb +3 -32
  40. data/lib/openc3/tools/table_manager/table_config.rb +9 -1
  41. data/lib/openc3/tools/table_manager/table_item_parser.rb +2 -2
  42. data/lib/openc3/top_level.rb +45 -19
  43. data/lib/openc3/topics/decom_interface_topic.rb +31 -0
  44. data/lib/openc3/utilities/authentication.rb +25 -6
  45. data/lib/openc3/utilities/cli_generator.rb +347 -3
  46. data/lib/openc3/utilities/env_helper.rb +10 -0
  47. data/lib/openc3/utilities/logger.rb +7 -11
  48. data/lib/openc3/version.rb +6 -6
  49. data/tasks/spec.rake +2 -1
  50. data/templates/command_validator/command_validator.py +49 -0
  51. data/templates/command_validator/command_validator.rb +54 -0
  52. data/templates/tool_angular/package.json +48 -2
  53. data/templates/tool_react/package.json +51 -1
  54. data/templates/tool_svelte/package.json +48 -1
  55. data/templates/tool_vue/package.json +36 -3
  56. data/templates/widget/package.json +28 -2
  57. metadata +8 -5
  58. data/templates/tool_vue/.browserslistrc +0 -16
  59. data/templates/widget/.browserslistrc +0 -16
@@ -17,6 +17,7 @@
17
17
  # if purchased from OpenC3, Inc.
18
18
 
19
19
  require 'json'
20
+ require 'openc3/config/config_parser'
20
21
 
21
22
  module OpenC3
22
23
  class Accessor
@@ -28,11 +29,26 @@ module OpenC3
28
29
  end
29
30
 
30
31
  def read_item(item, buffer)
31
- self.class.read_item(item, buffer)
32
+ if item.parent_item
33
+ # Structure is used to read items with parent, not accessor
34
+ structure_buffer = read_item(item.parent_item, buffer)
35
+ structure = item.parent_item.structure
36
+ structure.read(item.key, :RAW, structure_buffer)
37
+ else
38
+ self.class.read_item(item, buffer)
39
+ end
32
40
  end
33
41
 
34
42
  def write_item(item, value, buffer)
35
- self.class.write_item(item, value, buffer)
43
+ if item.parent_item
44
+ # Structure is used to write items with parent, not accessor
45
+ structure_buffer = read_item(item.parent_item, buffer)
46
+ structure = item.parent_item.structure
47
+ structure.write(item.key, value, :RAW, structure_buffer)
48
+ self.class.write_item(item.parent_item, structure_buffer, buffer)
49
+ else
50
+ self.class.write_item(item, value, buffer)
51
+ end
36
52
  end
37
53
 
38
54
  def read_items(items, buffer)
@@ -106,8 +122,16 @@ module OpenC3
106
122
  def self.convert_to_type(value, item)
107
123
  return value if value.nil?
108
124
  case item.data_type
125
+ when :ANY
126
+ begin
127
+ value = JSON.parse(value) if value.is_a? String
128
+ rescue Exception
129
+ # Just leave value as is
130
+ end
131
+ when :BOOL
132
+ value = ConfigParser.handle_true_false(value) if value.is_a? String
109
133
  when :OBJECT, :ARRAY
110
- # Do nothing for complex object types
134
+ value = JSON.parse(value) if value.is_a? String
111
135
  when :STRING, :BLOCK
112
136
  if item.array_size
113
137
  value = JSON.parse(value) if value.is_a? String
@@ -137,8 +137,16 @@ module OpenC3
137
137
 
138
138
  def read_item(item, buffer)
139
139
  return nil if item.data_type == :DERIVED
140
- handle_read_variable_bit_size(item, buffer) if item.variable_bit_size
141
- self.class.read_item(item, buffer)
140
+ if item.parent_item
141
+ handle_read_variable_bit_size(item.parent_item, buffer) if item.parent_item.variable_bit_size
142
+ # Structure is used to read items with parent, not accessor
143
+ structure_buffer = read_item(item.parent_item, buffer)
144
+ structure = item.parent_item.structure
145
+ structure.read(item.key, :RAW, structure_buffer)
146
+ else
147
+ handle_read_variable_bit_size(item, buffer) if item.variable_bit_size
148
+ self.class.read_item(item, buffer)
149
+ end
142
150
  end
143
151
 
144
152
  def handle_write_variable_bit_size(item, value, buffer)
@@ -270,8 +278,17 @@ module OpenC3
270
278
 
271
279
  def write_item(item, value, buffer)
272
280
  return nil if item.data_type == :DERIVED
273
- handle_write_variable_bit_size(item, value, buffer) if item.variable_bit_size
274
- self.class.write_item(item, value, buffer)
281
+ if item.parent_item
282
+ # Structure is used to write items with parent, not accessor
283
+ structure_buffer = read_item(item.parent_item, buffer)
284
+ structure = item.parent_item.structure
285
+ structure.write(item.key, value, :RAW, structure_buffer)
286
+ handle_write_variable_bit_size(item.parent_item, structure_buffer, buffer) if item.parent_item.variable_bit_size
287
+ self.class.write_item(item.parent_item, structure_buffer, buffer)
288
+ else
289
+ handle_write_variable_bit_size(item, value, buffer) if item.variable_bit_size
290
+ self.class.write_item(item, value, buffer)
291
+ end
275
292
  end
276
293
 
277
294
  # Note: do not use directly - use instance read_item
@@ -25,6 +25,7 @@ module OpenC3
25
25
  @left_char = left_char
26
26
  @right_char = right_char
27
27
  @configured = false
28
+ @args = [left_char, right_char]
28
29
  end
29
30
 
30
31
  def configure
@@ -65,7 +66,7 @@ module OpenC3
65
66
  values.each_with_index do |value, i|
66
67
  item_key = @item_keys[i]
67
68
  if item_key == item.key
68
- return Accessor.convert_to_type(value, item)
69
+ return self.class.convert_to_type(value, item)
69
70
  end
70
71
  end
71
72
  end
@@ -89,7 +90,7 @@ module OpenC3
89
90
  end
90
91
  index = @item_keys.index(item.key)
91
92
  if index
92
- result[item.name] = Accessor.convert_to_type(values[index], item)
93
+ result[item.name] = self.class.convert_to_type(values[index], item)
93
94
  else
94
95
  raise "Unknown item with key #{item.key} requested"
95
96
  end
@@ -545,9 +545,13 @@ module OpenC3
545
545
  queue = ENV['OPENC3_DEFAULT_QUEUE']
546
546
  end
547
547
  if queue
548
- # Pull the command out of the script string, e.g. cmd("INST ABORT")
549
- queued = cmd_string.split('("')[1].split('")')[0]
550
- QueueModel.queue_command(queue, command: queued, username: username, scope: scope)
548
+ # Pass the command components separately for the queue microservice to use the 3-parameter cmd() method
549
+ QueueModel.queue_command(queue,
550
+ target_name: target_name,
551
+ cmd_name: cmd_name,
552
+ cmd_params: cmd_params,
553
+ username: username,
554
+ scope: scope)
551
555
  else
552
556
  CommandTopic.send_command(command, timeout: timeout, scope: scope)
553
557
  end
@@ -221,15 +221,21 @@ module OpenC3
221
221
  # @param target_name [String] Name of the target
222
222
  # @param packet_name [String] Name of the packet
223
223
  # @return [Hash] telemetry hash with last telemetry buffer
224
- def get_tlm_buffer(*args, manual: false, scope: $openc3_scope, token: $openc3_token)
224
+ def get_tlm_buffer(*args, manual: false, scope: $openc3_scope, token: $openc3_token, timeout: 5)
225
225
  target_name, packet_name = _extract_target_packet_names('get_tlm_buffer', *args)
226
226
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
227
- TargetModel.packet(target_name, packet_name, scope: scope)
228
- topic = "#{scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
229
- msg_id, msg_hash = Topic.get_newest_message(topic)
230
- if msg_id
227
+ model = TargetModel.packet(target_name, packet_name, scope: scope)
228
+ if model["subpacket"]
229
+ msg_hash = DecomInterfaceTopic.get_tlm_buffer(target_name, packet_name, timeout: timeout, scope: scope)
231
230
  msg_hash['buffer'] = msg_hash['buffer'].b
232
231
  return msg_hash
232
+ else
233
+ topic = "#{scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
234
+ msg_id, msg_hash = Topic.get_newest_message(topic)
235
+ if msg_id
236
+ msg_hash['buffer'] = msg_hash['buffer'].b
237
+ return msg_hash
238
+ end
233
239
  end
234
240
  return nil
235
241
  end
@@ -249,7 +255,8 @@ module OpenC3
249
255
  packet = TargetModel.packet(target_name, packet_name, scope: scope)
250
256
  t = _validate_tlm_type(type)
251
257
  raise ArgumentError, "Unknown type '#{type}' for #{target_name} #{packet_name}" if t.nil?
252
- items = packet['items'].map { | item | item['name'].upcase }
258
+ items = packet["items"].reject { | item | item["hidden"] }
259
+ items = items.map { | item | item['name'].upcase }
253
260
  cvt_items = items.map { | item | [target_name, packet_name, item, type] }
254
261
  current_values = CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, cache_timeout: cache_timeout, scope: scope)
255
262
  items.zip(current_values).map { | item , values | [item, values[0], values[1]]}
@@ -437,8 +444,11 @@ module OpenC3
437
444
  # @param packet_name [String] Name of the packet
438
445
  # @param item_name [String] Name of the packet
439
446
  # @return [Hash] Telemetry packet item hash
440
- def get_item(*args, manual: false, scope: $openc3_scope, token: $openc3_token)
447
+ def get_item(*args, manual: false, scope: $openc3_scope, token: $openc3_token, cache_timeout: nil)
441
448
  target_name, packet_name, item_name = _extract_target_packet_item_names('get_item', *args)
449
+ if packet_name == 'LATEST'
450
+ packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope)
451
+ end
442
452
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
443
453
  TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
444
454
  end
@@ -89,12 +89,10 @@ module OpenC3
89
89
  begin
90
90
  if @telemetry
91
91
  target_packets = System.telemetry.packets(target_name)
92
- target = System.targets[target_name]
93
- unique_id_mode = target.tlm_unique_id_mode if target
92
+ unique_id_mode = System.telemetry.tlm_unique_id_mode(target_name)
94
93
  else
95
94
  target_packets = System.commands.packets(target_name)
96
- target = System.targets[target_name]
97
- unique_id_mode = target.cmd_unique_id_mode if target
95
+ unique_id_mode = System.commands.cmd_unique_id_mode(target_name)
98
96
  end
99
97
  rescue RuntimeError
100
98
  # No commands/telemetry for this target
@@ -103,7 +101,7 @@ module OpenC3
103
101
 
104
102
  if unique_id_mode
105
103
  target_packets.each do |_packet_name, packet|
106
- if packet.identify?(@data[@discard_leading_bytes..-1])
104
+ if not packet.subpacket and packet.identify?(@data[@discard_leading_bytes..-1]) # identify? handles virtual
107
105
  identified_packet = packet
108
106
  break
109
107
  end
@@ -111,15 +109,23 @@ module OpenC3
111
109
  else
112
110
  # Do a hash lookup to quickly identify the packet
113
111
  if target_packets.length > 0
114
- packet = target_packets.first[1]
115
- key = packet.read_id_values(@data[@discard_leading_bytes..-1])
116
- if @telemetry
117
- hash = System.telemetry.config.tlm_id_value_hash[target_name]
118
- else
119
- hash = System.commands.config.cmd_id_value_hash[target_name]
112
+ packet = nil
113
+ target_packets.each do |_packet_name, target_packet|
114
+ next if target_packet.virtual
115
+ next if target_packet.subpacket
116
+ packet = target_packet
117
+ break
118
+ end
119
+ if packet
120
+ key = packet.read_id_values(@data[@discard_leading_bytes..-1])
121
+ if @telemetry
122
+ hash = System.telemetry.config.tlm_id_value_hash[target_name]
123
+ else
124
+ hash = System.commands.config.cmd_id_value_hash[target_name]
125
+ end
126
+ identified_packet = hash[key]
127
+ identified_packet = hash['CATCHALL'.freeze] unless identified_packet
120
128
  end
121
- identified_packet = hash[key]
122
- identified_packet = hash['CATCHALL'.freeze] unless identified_packet
123
129
  end
124
130
  end
125
131
 
@@ -34,14 +34,16 @@ module OpenC3
34
34
  autoload(:TcpipServerInterface, 'openc3/interfaces/tcpip_server_interface.rb')
35
35
  autoload(:UdpInterface, 'openc3/interfaces/udp_interface.rb')
36
36
 
37
- autoload(:Protocol, 'openc3/interfaces/protocols/protocol.rb')
38
37
  autoload(:BurstProtocol, 'openc3/interfaces/protocols/burst_protocol.rb')
38
+ autoload(:CmdResponseProtocol, 'openc3/interfaces/protocols/cmd_response_protocol.rb')
39
+ autoload(:CobsProtocol, 'openc3/interfaces/protocols/cobs_protocol.rb')
40
+ autoload(:CrcProtocol, 'openc3/interfaces/protocols/crc_protocol.rb')
39
41
  autoload(:FixedProtocol, 'openc3/interfaces/protocols/fixed_protocol.rb')
42
+ autoload(:IgnorePacketProtocol, 'openc3/interfaces/protocols/ignore_packet_protocol.rb')
40
43
  autoload(:LengthProtocol, 'openc3/interfaces/protocols/length_protocol.rb')
41
44
  autoload(:PreidentifiedProtocol, 'openc3/interfaces/protocols/preidentified_protocol.rb')
45
+ autoload(:Protocol, 'openc3/interfaces/protocols/protocol.rb')
46
+ autoload(:SlipProtocol, 'openc3/interfaces/protocols/slip_protocol.rb')
42
47
  autoload(:TemplateProtocol, 'openc3/interfaces/protocols/template_protocol.rb')
43
48
  autoload(:TerminatedProtocol, 'openc3/interfaces/protocols/terminated_protocol.rb')
44
- autoload(:CmdResponseProtocol, 'openc3/interfaces/protocols/cmd_response_protocol.rb')
45
- autoload(:CrcProtocol, 'openc3/interfaces/protocols/crc_protocol.rb')
46
- autoload(:IgnorePacketProtocol, 'openc3/interfaces/protocols/ignore_packet_protocol.rb')
47
49
  end
@@ -59,6 +59,12 @@ class String
59
59
  NON_ASCII_PRINTABLE = /[^\x21-\x7e\s]/
60
60
  NON_UTF8_PRINTABLE = /[\x00-\x08\x0E-\x1F\x7F]/
61
61
  def as_json(_options = nil)
62
+ # If string is ASCII-8BIT (binary) and has non-ASCII bytes (> 127), encode as binary
63
+ # This handles data from hex_to_byte_string and other binary sources
64
+ if self.encoding == Encoding::ASCII_8BIT && self.bytes.any? { |b| b > 127 }
65
+ return self.to_json_raw_object
66
+ end
67
+
62
68
  as_utf8 = self.dup.force_encoding('UTF-8')
63
69
  if as_utf8.valid_encoding?
64
70
  if as_utf8 =~ NON_UTF8_PRINTABLE
@@ -24,6 +24,7 @@ require 'time'
24
24
  require 'thread'
25
25
  require 'openc3/microservices/microservice'
26
26
  require 'openc3/microservices/interface_decom_common'
27
+ require 'openc3/microservices/interface_microservice'
27
28
  require 'openc3/topics/telemetry_decom_topic'
28
29
  require 'openc3/topics/limits_event_topic'
29
30
 
@@ -127,6 +128,10 @@ module OpenC3
127
128
  handle_build_cmd(msg_hash['build_cmd'], msg_id)
128
129
  next
129
130
  end
131
+ if msg_hash.key?('get_tlm_buffer')
132
+ handle_get_tlm_buffer(msg_hash['get_tlm_buffer'], msg_id)
133
+ next
134
+ end
130
135
  else
131
136
  decom_packet(topic, msg_id, msg_hash, redis)
132
137
  @metric.set(name: 'decom_total', value: @count, type: 'counter')
@@ -154,9 +159,12 @@ module OpenC3
154
159
  @metric.set(name: 'decom_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and decom start')
155
160
 
156
161
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
162
+
163
+ #######################################
164
+ # Build packet object from topic data
165
+ #######################################
157
166
  target_name = msg_hash["target_name"]
158
167
  packet_name = msg_hash["packet_name"]
159
-
160
168
  packet = System.telemetry.packet(target_name, packet_name)
161
169
  packet.stored = ConfigParser.handle_true_false(msg_hash["stored"])
162
170
  # Note: Packet time will be recalculated as part of decom so not setting
@@ -168,29 +176,101 @@ module OpenC3
168
176
  packet.extra = extra
169
177
  end
170
178
  packet.buffer = msg_hash["buffer"]
171
- # Processors are user code points which must be rescued
172
- # so the TelemetryDecomTopic can write the packet
173
- begin
174
- packet.process # Run processors
175
- rescue Exception => e
176
- @error_count += 1
177
- @metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
178
- @error = e
179
- @logger.error e.message
180
- end
181
- # Process all the limits and call the limits_change_callback (as necessary)
182
- # check_limits also can call user code in the limits response
183
- # but that is rescued separately in the limits_change_callback
184
- packet.check_limits(System.limits_set)
185
179
 
186
- # This is what actually decommutates the packet and updates the CVT
187
- TelemetryDecomTopic.write_packet(packet, scope: @scope)
180
+ ################################################################################
181
+ # Break packet into subpackets (if necessary)
182
+ # Subpackets are typically channelized data
183
+ ################################################################################
184
+ packet_and_subpackets = packet.subpacketize
185
+
186
+ packet_and_subpackets.each do |packet_or_subpacket|
187
+ if packet_or_subpacket.subpacket
188
+ packet_or_subpacket = handle_subpacket(packet, packet_or_subpacket)
189
+ end
190
+
191
+ #####################################################################################
192
+ # Run Processors
193
+ # This must be before the full decom so that processor derived values are available
194
+ #####################################################################################
195
+ begin
196
+ packet_or_subpacket.process # Run processors
197
+ rescue Exception => e
198
+ @error_count += 1
199
+ @metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
200
+ @error = e
201
+ @logger.error e.message
202
+ end
203
+
204
+ #############################################################################
205
+ # Process all the limits and call the limits_change_callback (as necessary)
206
+ # This must be before the full decom so that limits states are available
207
+ #############################################################################
208
+ packet_or_subpacket.check_limits(System.limits_set)
209
+
210
+ # This is what actually decommutates the packet and updates the CVT
211
+ TelemetryDecomTopic.write_packet(packet_or_subpacket, scope: @scope)
212
+ end
188
213
 
189
214
  diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
190
215
  @metric.set(name: 'decom_duration_seconds', value: diff, type: 'gauge', unit: 'seconds')
191
216
  end
192
217
  end
193
218
 
219
+ def handle_subpacket(packet, subpacket)
220
+ # Subpacket received time always = packet.received_time
221
+ # Use packet_time appropriately if another timestamp is needed
222
+ subpacket.received_time = packet.received_time
223
+ subpacket.stored = packet.stored
224
+ subpacket.extra = packet.extra
225
+
226
+ if subpacket.stored
227
+ # Stored telemetry does not update the current value table
228
+ identified_subpacket = System.telemetry.identify_and_define_packet(subpacket, @target_names, subpackets: true)
229
+ else
230
+ # Identify and update subpacket
231
+ if subpacket.identified?
232
+ begin
233
+ # Preidentifed subpacket - place it into the current value table
234
+ identified_subpacket = System.telemetry.update!(subpacket.target_name,
235
+ subpacket.packet_name,
236
+ subpacket.buffer)
237
+ rescue RuntimeError
238
+ # Subpacket identified but we don't know about it
239
+ # Clear packet_name and target_name and try to identify
240
+ @logger.warn "#{@name}: Received unknown identified subpacket: #{subpacket.target_name} #{subpacket.packet_name}"
241
+ subpacket.target_name = nil
242
+ subpacket.packet_name = nil
243
+ identified_subpacket = System.telemetry.identify!(subpacket.buffer,
244
+ @target_names, subpackets: true)
245
+ end
246
+ else
247
+ # Packet needs to be identified
248
+ identified_subpacket = System.telemetry.identify!(subpacket.buffer,
249
+ @target_names, subpackets: true)
250
+ end
251
+ end
252
+
253
+ if identified_subpacket
254
+ identified_subpacket.received_time = subpacket.received_time
255
+ identified_subpacket.stored = subpacket.stored
256
+ identified_subpacket.extra = subpacket.extra
257
+ subpacket = identified_subpacket
258
+ else
259
+ unknown_subpacket = System.telemetry.update!('UNKNOWN', 'UNKNOWN', subpacket.buffer)
260
+ unknown_subpacket.received_time = subpacket.received_time
261
+ unknown_subpacket.stored = subpacket.stored
262
+ unknown_subpacket.extra = subpacket.extra
263
+ subpacket = unknown_subpacket
264
+ num_bytes_to_print = [InterfaceMicroservice::UNKNOWN_BYTES_TO_PRINT, subpacket.length].min
265
+ data = subpacket.buffer(false)[0..(num_bytes_to_print - 1)]
266
+ prefix = data.each_byte.map { | byte | sprintf("%02X", byte) }.join()
267
+ @logger.warn "#{@name} #{subpacket.target_name} packet length: #{subpacket.length} starting with: #{prefix}"
268
+ end
269
+
270
+ TargetModel.sync_tlm_packet_counts(subpacket, @target_names, scope: @scope)
271
+ return subpacket
272
+ end
273
+
194
274
  # Called when an item in any packet changes limits states.
195
275
  #
196
276
  # @param packet [Packet] Packet which has had an item change limits state
@@ -76,5 +76,37 @@ module OpenC3
76
76
  end
77
77
  Topic.write_topic(ack_topic, msg_hash)
78
78
  end
79
+
80
+ def handle_get_tlm_buffer(get_tlm_buffer_json, msg_id)
81
+ get_tlm_buffer_hash = JSON.parse(get_tlm_buffer_json, allow_nan: true, create_additions: true)
82
+ target_name = get_tlm_buffer_hash['target_name']
83
+ packet_name = get_tlm_buffer_hash['packet_name']
84
+ ack_topic = "{#{@scope}__ACKCMD}TARGET__#{target_name}"
85
+ begin
86
+ packet = System.telemetry.packet(target_name, packet_name)
87
+ msg_hash = {
88
+ id: msg_id,
89
+ result: 'SUCCESS',
90
+ time: packet.packet_time.to_nsec_from_epoch,
91
+ received_time: packet.received_time.to_nsec_from_epoch,
92
+ target_name: packet.target_name,
93
+ packet_name: packet.packet_name,
94
+ received_count: packet.received_count,
95
+ stored: packet.stored.to_s,
96
+ buffer: packet.buffer(false)
97
+ }
98
+ msg_hash[:extra] = JSON.generate(packet.extra.as_json, allow_nan: true) if packet.extra
99
+
100
+ # If there is an error due to parameter out of range, etc, we rescue it so we can
101
+ # write the ACKCMD}TARGET topic and allow the source to return
102
+ rescue => error
103
+ msg_hash = {
104
+ id: msg_id,
105
+ result: 'ERROR',
106
+ message: error.message
107
+ }
108
+ end
109
+ Topic.write_topic(ack_topic, msg_hash)
110
+ end
79
111
  end
80
112
  end
@@ -231,6 +231,8 @@ module OpenC3
231
231
  cmd_name = msg_hash['cmd_name']
232
232
  manual = ConfigParser.handle_true_false(msg_hash['manual'])
233
233
  cmd_params = nil
234
+ range_check = true
235
+ raw = false
234
236
  cmd_buffer = nil
235
237
  hazardous_check = nil
236
238
  if msg_hash['cmd_params']
@@ -288,14 +290,13 @@ module OpenC3
288
290
  if @critical_commanding and @critical_commanding != 'OFF' and not release_critical
289
291
  restricted = command.restricted
290
292
  if hazardous or restricted or (@critical_commanding == 'ALL' and manual)
293
+ cmd_type = 'NORMAL'
291
294
  if hazardous
292
- type = 'HAZARDOUS'
295
+ cmd_type = 'HAZARDOUS'
293
296
  elsif restricted
294
- type = 'RESTRICTED'
295
- else
296
- type = 'NORMAL'
297
+ cmd_type = 'RESTRICTED'
297
298
  end
298
- model = CriticalCmdModel.new(name: SecureRandom.uuid, type: type, interface_name: @interface.name, username: msg_hash['username'], cmd_hash: msg_hash, scope: @scope)
299
+ model = CriticalCmdModel.new(name: SecureRandom.uuid, type: cmd_type, interface_name: @interface.name, username: msg_hash['username'], cmd_hash: msg_hash, scope: @scope)
299
300
  model.create
300
301
  @logger.info("Critical Cmd Pending: #{msg_hash['cmd_string']}", user: msg_hash['username'], scope: @scope)
301
302
  next "CriticalCmdError\n#{model.name}"
@@ -428,10 +429,12 @@ module OpenC3
428
429
  params = JSON.parse(msg_hash['params'], allow_nan: true, create_additions: true)
429
430
  end
430
431
  @router = @tlm.attempting(*params)
432
+ next 'SUCCESS'
431
433
  end
432
434
  if msg_hash['disconnect']
433
435
  @logger.info "#{@router.name}: Disconnect requested"
434
436
  @tlm.disconnect(false)
437
+ next 'SUCCESS'
435
438
  end
436
439
  if msg_hash.key?('log_stream')
437
440
  if msg_hash['log_stream'] == 'true'
@@ -441,6 +444,7 @@ module OpenC3
441
444
  @logger.info "#{@router.name}: Disable stream logging"
442
445
  @router.stop_raw_logging
443
446
  end
447
+ next 'SUCCESS'
444
448
  end
445
449
  if msg_hash.key?('router_cmd')
446
450
  params = JSON.parse(msg_hash['router_cmd'], allow_nan: true, create_additions: true)
@@ -550,23 +554,7 @@ module OpenC3
550
554
  target = System.targets[target_name]
551
555
  target.interface = @interface
552
556
  end
553
- @interface.tlm_target_names.each do |target_name|
554
- # Initialize the target's packet counters based on the Topic stream
555
- # Prevents packet count resetting to 0 when interface restarts
556
- begin
557
- System.telemetry.packets(target_name).each do |packet_name, packet|
558
- topic = "#{@scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
559
- msg_id, msg_hash = Topic.get_newest_message(topic)
560
- if msg_id
561
- packet.received_count = msg_hash['received_count'].to_i
562
- else
563
- packet.received_count = 0
564
- end
565
- end
566
- rescue
567
- # Handle targets without telemetry
568
- end
569
- end
557
+ TargetModel.init_tlm_packet_counts(@interface.tlm_target_names, scope: @scope)
570
558
  if @interface.connect_on_startup
571
559
  @interface.state = 'ATTEMPTING'
572
560
  else
@@ -579,9 +567,6 @@ module OpenC3
579
567
  end
580
568
 
581
569
  @queued = false
582
- @sync_packet_count_data = {}
583
- @sync_packet_count_time = nil
584
- @sync_packet_count_delay_seconds = 1.0 # Sync packet counts every second
585
570
  @interface.options.each do |option_name, option_values|
586
571
  if option_name.upcase == 'OPTIMIZE_THROUGHPUT'
587
572
  @queued = true
@@ -590,7 +575,7 @@ module OpenC3
590
575
  StoreQueued.instance.set_update_interval(update_interval)
591
576
  end
592
577
  if option_name.upcase == 'SYNC_PACKET_COUNT_DELAY_SECONDS'
593
- @sync_packet_count_delay_seconds = option_values[0].to_f
578
+ TargetModel.sync_packet_count_delay_seconds = option_values[0].to_f
594
579
  end
595
580
  end
596
581
 
@@ -773,7 +758,7 @@ module OpenC3
773
758
 
774
759
  # Write to stream
775
760
  if @interface.tlm_target_enabled[packet.target_name]
776
- sync_tlm_packet_counts(packet)
761
+ TargetModel.sync_tlm_packet_counts(packet, @interface.tlm_target_names, scope: @scope)
777
762
  TelemetryTopic.write_packet(packet, queued: @queued, scope: @scope)
778
763
  end
779
764
  end
@@ -908,59 +893,6 @@ module OpenC3
908
893
  def graceful_kill
909
894
  # Just to avoid warning
910
895
  end
911
-
912
- def sync_tlm_packet_counts(packet)
913
- if @sync_packet_count_delay_seconds <= 0 or $openc3_redis_cluster
914
- # Perfect but slow method
915
- packet.received_count = TargetModel.increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: @scope)
916
- else
917
- # Eventually consistent method
918
- # Only sync every period (default 1 second) to avoid hammering Redis
919
- # This is a trade off between speed and accuracy
920
- # The packet count is eventually consistent
921
- @sync_packet_count_data[packet.target_name] ||= {}
922
- @sync_packet_count_data[packet.target_name][packet.packet_name] ||= 0
923
- @sync_packet_count_data[packet.target_name][packet.packet_name] += 1
924
-
925
- # Ensures counters change between syncs
926
- packet.received_count += 1
927
-
928
- # Check if we need to sync the packet counts
929
- if @sync_packet_count_time.nil? or (Time.now - @sync_packet_count_time) > @sync_packet_count_delay_seconds
930
- @sync_packet_count_time = Time.now
931
-
932
- inc_count = 0
933
- # Use pipeline to make this one transaction
934
- result = Store.redis_pool.pipelined do
935
- # Increment global counters for packets received
936
- @sync_packet_count_data.each do |target_name, packet_data|
937
- packet_data.each do |packet_name, count|
938
- TargetModel.increment_telemetry_count(target_name, packet_name, count, scope: @scope)
939
- inc_count += 1
940
- end
941
- end
942
- @sync_packet_count_data = {}
943
-
944
- # Get all the packet counts with the global counters
945
- @interface.tlm_target_names.each do |target_name|
946
- TargetModel.get_all_telemetry_counts(target_name, scope: @scope)
947
- end
948
- TargetModel.get_all_telemetry_counts('UNKNOWN', scope: @scope)
949
- end
950
- @interface.tlm_target_names.each do |target_name|
951
- result[inc_count].each do |packet_name, count|
952
- update_packet = System.telemetry.packet(target_name, packet_name)
953
- update_packet.received_count = count.to_i
954
- end
955
- inc_count += 1
956
- end
957
- result[inc_count].each do |packet_name, count|
958
- update_packet = System.telemetry.packet('UNKNOWN', packet_name)
959
- update_packet.received_count = count.to_i
960
- end
961
- end
962
- end
963
- end
964
896
  end
965
897
  end
966
898