openc3 6.9.2 → 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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/data/config/command_modifiers.yaml +79 -0
  3. data/data/config/item_modifiers.yaml +5 -0
  4. data/data/config/parameter_modifiers.yaml +5 -0
  5. data/data/config/telemetry_modifiers.yaml +79 -0
  6. data/ext/openc3/ext/packet/packet.c +9 -0
  7. data/lib/openc3/accessors/accessor.rb +27 -3
  8. data/lib/openc3/accessors/binary_accessor.rb +21 -4
  9. data/lib/openc3/accessors/template_accessor.rb +3 -2
  10. data/lib/openc3/api/cmd_api.rb +7 -3
  11. data/lib/openc3/api/tlm_api.rb +17 -7
  12. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +19 -13
  13. data/lib/openc3/io/json_rpc.rb +6 -0
  14. data/lib/openc3/microservices/decom_microservice.rb +97 -17
  15. data/lib/openc3/microservices/interface_decom_common.rb +32 -0
  16. data/lib/openc3/microservices/interface_microservice.rb +3 -75
  17. data/lib/openc3/microservices/queue_microservice.rb +16 -1
  18. data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +23 -0
  19. data/lib/openc3/models/plugin_model.rb +7 -1
  20. data/lib/openc3/models/queue_model.rb +32 -5
  21. data/lib/openc3/models/reaction_model.rb +25 -9
  22. data/lib/openc3/models/target_model.rb +78 -10
  23. data/lib/openc3/packets/commands.rb +33 -7
  24. data/lib/openc3/packets/packet.rb +75 -71
  25. data/lib/openc3/packets/packet_config.rb +78 -29
  26. data/lib/openc3/packets/packet_item.rb +11 -103
  27. data/lib/openc3/packets/parsers/packet_item_parser.rb +177 -34
  28. data/lib/openc3/packets/parsers/xtce_converter.rb +2 -2
  29. data/lib/openc3/packets/structure.rb +29 -21
  30. data/lib/openc3/packets/structure_item.rb +31 -19
  31. data/lib/openc3/packets/telemetry.rb +37 -11
  32. data/lib/openc3/script/suite_results.rb +2 -2
  33. data/lib/openc3/subpacketizers/subpacketizer.rb +18 -0
  34. data/lib/openc3/system/target.rb +3 -32
  35. data/lib/openc3/tools/table_manager/table_config.rb +9 -1
  36. data/lib/openc3/tools/table_manager/table_item_parser.rb +2 -2
  37. data/lib/openc3/top_level.rb +45 -19
  38. data/lib/openc3/topics/decom_interface_topic.rb +31 -0
  39. data/lib/openc3/utilities/env_helper.rb +10 -0
  40. data/lib/openc3/utilities/logger.rb +7 -11
  41. data/lib/openc3/version.rb +6 -6
  42. data/tasks/spec.rake +2 -1
  43. data/templates/tool_angular/package.json +2 -2
  44. data/templates/tool_react/package.json +1 -1
  45. data/templates/tool_svelte/package.json +1 -1
  46. data/templates/tool_vue/package.json +3 -3
  47. data/templates/widget/package.json +2 -2
  48. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d4fd7006c78d3a21531579d68d42bc5cba596c92091e8cd7895c4b42ecff569
4
- data.tar.gz: 85fdaa738dafbfafcc16fb463abb5d86517f9f1edefb4bbef9a5e7171738211c
3
+ metadata.gz: 1432f7bc925107320f327e82f47ebe6d6da648d00fc6c6469f4f093e69a8dc96
4
+ data.tar.gz: 5aea790bee69e50decbbf418b295eda17a653305f78661c334531afa94f9d265
5
5
  SHA512:
6
- metadata.gz: 419ddee4f6203f3500c9d3eb45347824dfa57a074527086fd8bfdaee16c4dc10ec07b3b78122e1a753a064913bbb1193f29344e260ba7a04b029cdb11682a9e7
7
- data.tar.gz: 961d67c9c972a2cdf60c31c9279b69a7e1fc315e62813935eb66fefbbd853cf931c633c36aa6b9606cb1eedfd66ef8ba2db07b34f24acf99967aa6983755deb3
6
+ metadata.gz: dbb54c3b45300cc626c4497ef71c7069567dad94d6f6c73fb1a7dd7093219531d31ffee8bd175ac08b9fee682ab93a488c796d3bf408365a64027740ed9913a3
7
+ data.tar.gz: a703fe408f123953eccf8e9e2b6fb63306404501456a5463410babb95512c68387440279e0d91e61b674ea3bf72122d3480d0001d994cf943d6b901f1a10cc08
@@ -96,6 +96,68 @@ APPEND_ARRAY_PARAMETER:
96
96
  values: .*
97
97
  <%= MetaConfigParser.load('_array_params.yaml').to_meta_config_yaml(4) %>
98
98
  example: APPEND_ARRAY_PARAMETER ARRAY 64 FLOAT 640 "Array of 10 64bit floats"
99
+ STRUCTURE:
100
+ modifiers:
101
+ <%= MetaConfigParser.load('parameter_modifiers.yaml').to_meta_config_yaml(4) %>
102
+ summary: Adds and flattens a structure (generally a virtual packet) into the current packet. The specific named item is BLOCK type and hidden.
103
+ parameters:
104
+ - name: Name
105
+ required: true
106
+ description: Name of the parameter. Must be unique within the command.
107
+ values: .*
108
+ - name: Bit Offset
109
+ required: true
110
+ description: Bit offset into the command packet of the Most Significant Bit of this parameter.
111
+ May be negative to indicate an offset from the end of the packet.
112
+ Always use a bit offset of 0 for derived parameters.
113
+ values: '[-]?\d+'
114
+ - name: Bit Size
115
+ required: true
116
+ description: Bit size of this parameter. Zero or Negative values may be used
117
+ to indicate that a string fills the packet up to the offset from the end of
118
+ the packet specified by this value. If Bit Offset is 0 and Bit Size is 0 then
119
+ this is a derived parameter and the Data Type must be set to 'DERIVED'.
120
+ values: \d+
121
+ - name: Command or telemetry
122
+ required: true
123
+ description: Whether the structure packet is a command or telemetry packet
124
+ values: <%= %w(CMD COMMAND TLM TELEMETRY) %>
125
+ - name: Target Name
126
+ required: true
127
+ description: Target Name of the structure packet
128
+ values: .+
129
+ - name: Packet Name
130
+ required: true
131
+ description: Packet Name of the structure packet
132
+ values: .+
133
+ APPEND_STRUCTURE:
134
+ modifiers:
135
+ <%= MetaConfigParser.load('parameter_modifiers.yaml').to_meta_config_yaml(4) %>
136
+ summary: Adds and flattens a structure (generally a virtual packet) into the current packet. The specific named item is BLOCK type and hidden.
137
+ parameters:
138
+ - name: Name
139
+ required: true
140
+ description: Name of the parameter. Must be unique within the command.
141
+ values: .*
142
+ - name: Bit Size
143
+ required: true
144
+ description: Bit size of this parameter. Zero or Negative values may be used
145
+ to indicate that a string fills the packet up to the offset from the end of
146
+ the packet specified by this value. If Bit Offset is 0 and Bit Size is 0 then
147
+ this is a derived parameter and the Data Type must be set to 'DERIVED'.
148
+ values: \d+
149
+ - name: Command or telemetry
150
+ required: true
151
+ description: Whether the structure packet is a command or telemetry packet
152
+ values: <%= %w(CMD COMMAND TLM TELEMETRY) %>
153
+ - name: Target Name
154
+ required: true
155
+ description: Target Name of the structure packet
156
+ values: .+
157
+ - name: Packet Name
158
+ required: true
159
+ description: Packet Name of the structure packet
160
+ values: .+
99
161
  SELECT_PARAMETER:
100
162
  modifiers:
101
163
  <%= MetaConfigParser.load('parameter_modifiers.yaml').to_meta_config_yaml(4) %>
@@ -176,6 +238,19 @@ ACCESSOR:
176
238
  description: Additional argument passed to the accessor class constructor
177
239
  values: .+
178
240
  since: 5.0.10
241
+ SUBPACKETIZER:
242
+ summary: Defines a class used to break up the packet into subpackets before decom
243
+ description: Defines a class used to break up the packet into subpackets before decom. Defaults to nil/None.
244
+ parameters:
245
+ - name: Subpacketizer Class Name
246
+ required: true
247
+ description: The name of the Subpacketizer class
248
+ values: .+
249
+ - name: Argument
250
+ required: false
251
+ description: Additional argument passed to the Subpacketizer class constructor
252
+ values: .+
253
+ since: 6.10.0
179
254
  TEMPLATE:
180
255
  summary: Defines a template string used to initialize the command before default values are filled in
181
256
  description: Generally the template string is formatted in JSON or HTML and then values are filled in with
@@ -256,6 +331,10 @@ RESTRICTED:
256
331
  summary: Marks this packet as restricted and will require approval if critical commanding is enabled
257
332
  description: Used as one of the two types of critical commands (HAZARDOUS and RESTRICTED)
258
333
  since: 5.20.0
334
+ SUBPACKET:
335
+ summary: Marks this packet as as a subpacket which will exclude it from Interface level identification
336
+ description: Used with a SUBPACKETIZER to breakup up packets into subpackets at decom time
337
+ since: 6.10.0
259
338
  VALIDATOR:
260
339
  summary: Defines a validator class for a command
261
340
  description: Validator class is used to validate the command success or failure with both a pre_check and post_check method.
@@ -176,3 +176,8 @@ LIMITS_RESPONSE:
176
176
  description: Variable length number of options that will be passed to the
177
177
  class constructor
178
178
  values: .+
179
+ HIDDEN:
180
+ summary: Hides this item from all the OpenC3 tools
181
+ description: This item will not appear in PacketViewer or Item Choosers.
182
+ It also hides this item from appearing in the Script Runner popup helper
183
+ when writing scripts. The item will also not be included in decom data.
@@ -167,3 +167,8 @@ OVERFLOW:
167
167
  description: How OpenC3 treats an overflow value. Only applies to signed and
168
168
  unsigned integer data types.
169
169
  values: <%= %w(ERROR ERROR_ALLOW_HEX TRUNCATE SATURATE) %>
170
+ HIDDEN:
171
+ summary: Hides this parameter from all the OpenC3 tools
172
+ description: This item will not appear in CmdSender.
173
+ It also hides this item from appearing in the Script Runner popup helper
174
+ when writing scripts. The parameter should not be provided to commands.
@@ -86,6 +86,68 @@ APPEND_ARRAY_ITEM:
86
86
  description: Name of the telemety item. Must be unique within the packet.
87
87
  values: \'
88
88
  <%= MetaConfigParser.load('_array_params.yaml').to_meta_config_yaml(4) %>
89
+ STRUCTURE:
90
+ modifiers:
91
+ <%= MetaConfigParser.load('item_modifiers.yaml').to_meta_config_yaml(4) %>
92
+ summary: Adds and flattens a structure (generally a virtual packet) into the current packet. The specific named item is BLOCK type and hidden.
93
+ parameters:
94
+ - name: Name
95
+ required: true
96
+ description: Name of the parameter. Must be unique within the command.
97
+ values: .*
98
+ - name: Bit Offset
99
+ required: true
100
+ description: Bit offset into the command packet of the Most Significant Bit of this parameter.
101
+ May be negative to indicate an offset from the end of the packet.
102
+ Always use a bit offset of 0 for derived parameters.
103
+ values: '[-]?\d+'
104
+ - name: Bit Size
105
+ required: true
106
+ description: Bit size of this parameter. Zero or Negative values may be used
107
+ to indicate that a string fills the packet up to the offset from the end of
108
+ the packet specified by this value. If Bit Offset is 0 and Bit Size is 0 then
109
+ this is a derived parameter and the Data Type must be set to 'DERIVED'.
110
+ values: \d+
111
+ - name: Command or telemetry
112
+ required: true
113
+ description: Whether the structure packet is a command or telemetry packet
114
+ values: <%= %w(CMD COMMAND TLM TELEMETRY) %>
115
+ - name: Target Name
116
+ required: true
117
+ description: Target Name of the structure packet
118
+ values: .+
119
+ - name: Packet Name
120
+ required: true
121
+ description: Packet Name of the structure packet
122
+ values: .+
123
+ APPEND_STRUCTURE:
124
+ modifiers:
125
+ <%= MetaConfigParser.load('item_modifiers.yaml').to_meta_config_yaml(4) %>
126
+ summary: Adds and flattens a structure (generally a virtual packet) into the current packet. The specific named item is BLOCK type and hidden.
127
+ parameters:
128
+ - name: Name
129
+ required: true
130
+ description: Name of the parameter. Must be unique within the command.
131
+ values: .*
132
+ - name: Bit Size
133
+ required: true
134
+ description: Bit size of this parameter. Zero or Negative values may be used
135
+ to indicate that a string fills the packet up to the offset from the end of
136
+ the packet specified by this value. If Bit Offset is 0 and Bit Size is 0 then
137
+ this is a derived parameter and the Data Type must be set to 'DERIVED'.
138
+ values: \d+
139
+ - name: Command or telemetry
140
+ required: true
141
+ description: Whether the structure packet is a command or telemetry packet
142
+ values: <%= %w(CMD COMMAND TLM TELEMETRY) %>
143
+ - name: Target Name
144
+ required: true
145
+ description: Target Name of the structure packet
146
+ values: .+
147
+ - name: Packet Name
148
+ required: true
149
+ description: Packet Name of the structure packet
150
+ values: .+
89
151
  SELECT_ITEM:
90
152
  modifiers:
91
153
  <%= MetaConfigParser.load('item_modifiers.yaml').to_meta_config_yaml(4) %>
@@ -173,6 +235,19 @@ ACCESSOR:
173
235
  description: The name of the accessor class
174
236
  values: .+
175
237
  since: 5.0.10
238
+ SUBPACKETIZER:
239
+ summary: Defines a class used to break up the packet into subpackets before decom
240
+ description: Defines a class used to break up the packet into subpackets before decom. Defaults to nil/None.
241
+ parameters:
242
+ - name: Subpacketizer Class Name
243
+ required: true
244
+ description: The name of the Subpacketizer class
245
+ values: .+
246
+ - name: Argument
247
+ required: false
248
+ description: Additional argument passed to the Subpacketizer class constructor
249
+ values: .+
250
+ since: 6.10.0
176
251
  TEMPLATE:
177
252
  summary: Defines a template string used to pull telemetry values from a string buffer
178
253
  parameters:
@@ -198,3 +273,7 @@ VIRTUAL:
198
273
  summary: Marks this packet as virtual and not participating in identification
199
274
  description: Used for packet definitions that can be used as structures for items with a given packet.
200
275
  since: 5.18.0
276
+ SUBPACKET:
277
+ summary: Marks this packet as as a subpacket which will exclude it from Interface level identification
278
+ description: Used with a SUBPACKETIZER to breakup up packets into subpackets at decom time
279
+ since: 6.10.0
@@ -64,6 +64,9 @@ static ID id_ivar_packet_time = 0;
64
64
  static ID id_ivar_ignore_overlap = 0;
65
65
  static ID id_ivar_virtual = 0;
66
66
  static ID id_ivar_restricted = 0;
67
+ static ID id_ivar_subpacket = 0;
68
+ static ID id_ivar_subpacketizer = 0;
69
+ static ID id_ivar_obfuscated_items = 0;
67
70
 
68
71
  /* Sets the target name this packet is associated with. Unidentified packets
69
72
  * will have target name set to nil.
@@ -291,6 +294,9 @@ static VALUE packet_initialize(int argc, VALUE *argv, VALUE self)
291
294
  rb_ivar_set(self, id_ivar_ignore_overlap, Qfalse);
292
295
  rb_ivar_set(self, id_ivar_virtual, Qfalse);
293
296
  rb_ivar_set(self, id_ivar_restricted, Qfalse);
297
+ rb_ivar_set(self, id_ivar_subpacket, Qfalse);
298
+ rb_ivar_set(self, id_ivar_subpacketizer, Qnil);
299
+ rb_ivar_set(self, id_ivar_obfuscated_items, Qnil);
294
300
  return self;
295
301
  }
296
302
 
@@ -335,6 +341,9 @@ void Init_packet(void)
335
341
  id_ivar_ignore_overlap = rb_intern("@ignore_overlap");
336
342
  id_ivar_virtual = rb_intern("@virtual");
337
343
  id_ivar_restricted = rb_intern("@restricted");
344
+ id_ivar_subpacket = rb_intern("@subpacket");
345
+ id_ivar_subpacketizer = rb_intern("@subpacketizer");
346
+ id_ivar_obfuscated_items = rb_intern("@obfuscated_items");
338
347
 
339
348
  cPacket = rb_define_class_under(mOpenC3, "Packet", cStructure);
340
349
  rb_define_method(cPacket, "initialize", packet_initialize, -1);
@@ -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
 
@@ -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