cosmos 3.1.2 → 3.2.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/Manifest.txt +17 -1
  4. data/autohotkey/tools/test_runner2.ahk +1 -0
  5. data/autohotkey/tools/tlm_grapher.ahk +13 -1
  6. data/data/crc.txt +39 -30
  7. data/demo/config/data/crc.txt +3 -3
  8. data/demo/config/targets/TEMPLATED/lib/templated_interface.rb +3 -1
  9. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +7 -1
  10. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +6 -1
  11. data/lib/cosmos.rb +2 -2
  12. data/lib/cosmos/gui/dialogs/about_dialog.rb +18 -5
  13. data/lib/cosmos/gui/dialogs/tlm_details_dialog.rb +0 -7
  14. data/lib/cosmos/gui/line_graph/overview_graph.rb +12 -2
  15. data/lib/cosmos/gui/utilities/script_module_gui.rb +11 -3
  16. data/lib/cosmos/interfaces/interface.rb +12 -0
  17. data/lib/cosmos/interfaces/stream_interface.rb +1 -21
  18. data/lib/cosmos/interfaces/tcpip_server_interface.rb +10 -0
  19. data/lib/cosmos/io/json_drb_object.rb +75 -56
  20. data/lib/cosmos/io/tcpip_server.rb +1 -11
  21. data/lib/cosmos/packet_logs.rb +1 -0
  22. data/lib/cosmos/packet_logs/ccsds_log_reader.rb +103 -0
  23. data/lib/cosmos/packets/packet.rb +70 -1
  24. data/lib/cosmos/packets/packet_config.rb +59 -611
  25. data/lib/cosmos/packets/parsers/format_string_parser.rb +58 -0
  26. data/lib/cosmos/packets/parsers/limits_parser.rb +146 -0
  27. data/lib/cosmos/packets/parsers/limits_response_parser.rb +52 -0
  28. data/lib/cosmos/packets/parsers/macro_parser.rb +116 -0
  29. data/lib/cosmos/packets/parsers/packet_item_parser.rb +215 -0
  30. data/lib/cosmos/packets/parsers/packet_parser.rb +123 -0
  31. data/lib/cosmos/packets/parsers/processor_parser.rb +63 -0
  32. data/lib/cosmos/packets/parsers/state_parser.rb +116 -0
  33. data/lib/cosmos/packets/structure.rb +59 -22
  34. data/lib/cosmos/packets/structure_item.rb +1 -1
  35. data/lib/cosmos/script/script.rb +4 -5
  36. data/lib/cosmos/streams/serial_stream.rb +5 -0
  37. data/lib/cosmos/streams/stream.rb +8 -2
  38. data/lib/cosmos/streams/stream_protocol.rb +1 -0
  39. data/lib/cosmos/streams/tcpip_client_stream.rb +37 -7
  40. data/lib/cosmos/streams/tcpip_socket_stream.rb +9 -6
  41. data/lib/cosmos/system/target.rb +3 -6
  42. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +57 -48
  43. data/lib/cosmos/tools/cmd_tlm_server/interface_thread.rb +7 -3
  44. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +1 -1
  45. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +7 -1
  46. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +1 -2
  47. data/lib/cosmos/top_level.rb +22 -11
  48. data/lib/cosmos/utilities/message_log.rb +14 -9
  49. data/lib/cosmos/version.rb +5 -5
  50. data/spec/interfaces/cmd_tlm_server_interface_spec.rb +16 -16
  51. data/spec/interfaces/linc_interface_spec.rb +3 -0
  52. data/spec/interfaces/tcpip_client_interface_spec.rb +1 -0
  53. data/spec/interfaces/tcpip_server_interface_spec.rb +9 -0
  54. data/spec/io/json_drb_object_spec.rb +1 -1
  55. data/spec/io/serial_driver_spec.rb +0 -1
  56. data/spec/packet_logs/packet_log_writer_spec.rb +5 -3
  57. data/spec/packets/packet_config_spec.rb +22 -837
  58. data/spec/packets/packet_item_spec.rb +10 -10
  59. data/spec/packets/packet_spec.rb +239 -1
  60. data/spec/packets/parsers/format_string_parser_spec.rb +122 -0
  61. data/spec/packets/parsers/limits_parser_spec.rb +282 -0
  62. data/spec/packets/parsers/limits_response_parser_spec.rb +149 -0
  63. data/spec/packets/parsers/macro_parser_spec.rb +184 -0
  64. data/spec/packets/parsers/packet_item_parser_spec.rb +306 -0
  65. data/spec/packets/parsers/packet_parser_spec.rb +99 -0
  66. data/spec/packets/parsers/processor_parser_spec.rb +114 -0
  67. data/spec/packets/parsers/state_parser_spec.rb +156 -0
  68. data/spec/packets/structure_item_spec.rb +14 -14
  69. data/spec/packets/structure_spec.rb +162 -16
  70. data/spec/streams/fixed_stream_protocol_spec.rb +7 -4
  71. data/spec/streams/length_stream_protocol_spec.rb +3 -0
  72. data/spec/streams/preidentified_stream_protocol_spec.rb +3 -0
  73. data/spec/streams/serial_stream_spec.rb +12 -0
  74. data/spec/streams/stream_protocol_spec.rb +14 -0
  75. data/spec/streams/stream_spec.rb +1 -0
  76. data/spec/streams/tcpip_client_stream_spec.rb +3 -0
  77. data/spec/streams/tcpip_socket_stream_spec.rb +15 -3
  78. data/spec/streams/template_stream_protocol_spec.rb +5 -0
  79. data/spec/streams/terminated_stream_protocol_spec.rb +4 -0
  80. data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +21 -1
  81. data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +1 -1
  82. data/spec/tools/cmd_tlm_server/interfaces_spec.rb +1 -1
  83. metadata +19 -3
@@ -165,6 +165,53 @@ module Cosmos
165
165
  end
166
166
  end
167
167
 
168
+ # Review bit offset to look for overlapping definitions. This will allow
169
+ # gaps in the packet, but not allow the same bits to be used for multiple
170
+ # variables.
171
+ #
172
+ # @return [Array<String>] Warning messages for big definition overlaps
173
+ def check_bit_offsets
174
+ expected_next_offset = nil
175
+ previous_item = nil
176
+ warnings = []
177
+ @sorted_items.each do |item|
178
+ if expected_next_offset and item.bit_offset < expected_next_offset
179
+ msg = "Bit definition overlap at bit offset #{item.bit_offset} for packet #{@target_name} #{@packet_name} items #{item.name} and #{previous_item.name}"
180
+ Logger.instance.warn(msg)
181
+ warnings << msg
182
+ end
183
+ if item.array_size
184
+ if item.array_size > 0
185
+ expected_next_offset = item.bit_offset + item.array_size
186
+ else
187
+ expected_next_offset = item.array_size
188
+ end
189
+ else
190
+ expected_next_offset = nil
191
+ if item.bit_offset > 0
192
+ # Handle little-endian bit fields
193
+ byte_aligned = ((item.bit_offset % 8) == 0)
194
+ if item.endianness == :LITTLE_ENDIAN and (item.data_type == :INT or item.data_type == :UINT) and !(byte_aligned and (item.bit_size == 8 or item.bit_size == 16 or item.bit_size == 32 or item.bit_size == 64))
195
+ # Bit offset always refers to the most significant bit of a bitfield
196
+ bits_remaining_in_last_byte = 8 - (item.bit_offset % 8)
197
+ if item.bit_size > bits_remaining_in_last_byte
198
+ expected_next_offset = item.bit_offset + bits_remaining_in_last_byte
199
+ end
200
+ end
201
+ end
202
+ unless expected_next_offset
203
+ if item.bit_size > 0
204
+ expected_next_offset = item.bit_offset + item.bit_size
205
+ else
206
+ expected_next_offset = item.bit_size
207
+ end
208
+ end
209
+ end
210
+ previous_item = item
211
+ end
212
+ warnings
213
+ end
214
+
168
215
  # Id items are used by the identify? method to determine if a raw buffer of
169
216
  # data represents this packet.
170
217
  # @return [Array<PacketItem>] Packet item identifiers
@@ -218,11 +265,24 @@ module Cosmos
218
265
  # @param id_value [Object] Set to something other than nil to indicate that
219
266
  # this item should be used to identify a buffer as this packet. The
220
267
  # id_value should make sense according to the data_type.
268
+ # @return [PacketItem] The new packet item
221
269
  def define_item(name, bit_offset, bit_size, data_type, array_size = nil, endianness = @default_endianness, overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
222
270
  item = super(name, bit_offset, bit_size, data_type, array_size, endianness, overflow)
223
271
  packet_define_item(item, format_string, read_conversion, write_conversion, id_value)
224
272
  end
225
273
 
274
+ # Add an item to the packet by adding it to the items hash. It also
275
+ # resizes the buffer to accomodate the new item.
276
+ #
277
+ # @param item [PacketItem] Item to add to the packet
278
+ # @return [PacketItem] The same packet item
279
+ def define(item)
280
+ item = super(item)
281
+ update_id_items(item)
282
+ update_limits_items_cache()
283
+ item
284
+ end
285
+
226
286
  # Define an item at the end of the packet. This creates a new instance of the
227
287
  # item_class as given in the constructor and adds it to the items hash. It
228
288
  # also resizes the buffer to accomodate the new item.
@@ -237,6 +297,7 @@ module Cosmos
237
297
  # @param read_conversion (see #define_item)
238
298
  # @param write_conversion (see #define_item)
239
299
  # @param id_value (see #define_item)
300
+ # @return (see #define_item)
240
301
  def append_item(name, bit_size, data_type, array_size = nil, endianness = @default_endianness, overflow = :ERROR, format_string = nil, read_conversion = nil, write_conversion = nil, id_value = nil)
241
302
  item = super(name, bit_size, data_type, array_size, endianness, overflow)
242
303
  packet_define_item(item, format_string, read_conversion, write_conversion, id_value)
@@ -569,6 +630,7 @@ module Cosmos
569
630
  def clone
570
631
  packet = super()
571
632
  if packet.instance_variable_get("@processors")
633
+ packet.instance_variable_set("@processors", packet.processors.clone)
572
634
  packet.processors.each do |processor_name, processor|
573
635
  packet.processors[processor_name] = processor.clone
574
636
  end
@@ -703,12 +765,19 @@ module Cosmos
703
765
  # Change id_value to the correct type
704
766
  if id_value
705
767
  item.id_value = id_value
768
+ update_id_items(item)
769
+ end
770
+ item
771
+ end
772
+
773
+ def update_id_items(item)
774
+ if item.id_value
706
775
  @id_items ||= []
707
776
  @id_items << item
708
777
  end
709
-
710
778
  item
711
779
  end
780
+
712
781
  end # class Packet
713
782
 
714
783
  end # module Cosmos
@@ -10,9 +10,16 @@
10
10
 
11
11
  require 'cosmos/config/config_parser'
12
12
  require 'cosmos/packets/packet'
13
+ require 'cosmos/packets/parsers/packet_parser'
14
+ require 'cosmos/packets/parsers/packet_item_parser'
15
+ require 'cosmos/packets/parsers/macro_parser'
16
+ require 'cosmos/packets/parsers/limits_parser'
17
+ require 'cosmos/packets/parsers/limits_response_parser'
18
+ require 'cosmos/packets/parsers/state_parser'
19
+ require 'cosmos/packets/parsers/format_string_parser'
20
+ require 'cosmos/packets/parsers/processor_parser'
13
21
  require 'cosmos/conversions'
14
22
  require 'cosmos/processors'
15
- require 'ostruct'
16
23
 
17
24
  module Cosmos
18
25
 
@@ -49,6 +56,9 @@ module Cosmos
49
56
  # packet is not.
50
57
  attr_reader :latest_data
51
58
 
59
+ COMMAND = "Command"
60
+ TELEMETRY = "Telemetry"
61
+
52
62
  def initialize
53
63
  @name = nil
54
64
  @telemetry = {}
@@ -69,8 +79,6 @@ module Cosmos
69
79
  @telemetry['UNKNOWN']['UNKNOWN'] = Packet.new('UNKNOWN', 'UNKNOWN', :BIG_ENDIAN)
70
80
 
71
81
  # Used during packet processing
72
- @current_target_name = nil
73
- @current_packet_name = nil
74
82
  @current_cmd_or_tlm = nil
75
83
  @current_packet = nil
76
84
  @current_item = nil
@@ -86,20 +94,13 @@ module Cosmos
86
94
  #
87
95
  # @param filename [String] The name of the configuration file
88
96
  # @param target_name [String] The target name
89
- def process_file(filename, target_name)
97
+ def process_file(filename, process_target_name)
90
98
  @converted_type = nil
91
99
  @converted_bit_size = nil
92
100
  @proc_text = ''
93
101
  @building_generic_conversion = false
94
- @macro_append = OpenStruct.new
95
- @macro_append.building = false
96
- @macro_append.list = []
97
- @macro_append.indices = []
98
- @macro_append.format = ''
99
- @macro_append.format_order = ''
100
-
101
- target_name = target_name.upcase
102
102
 
103
+ process_target_name = process_target_name.upcase
103
104
  parser = ConfigParser.new("https://github.com/BallAerospace/COSMOS/wiki/Command-and-Telemetry-Configuration")
104
105
  parser.parse_file(filename) do |keyword, params|
105
106
 
@@ -127,28 +128,35 @@ module Cosmos
127
128
  case keyword
128
129
 
129
130
  # Start a new packet
130
- when 'COMMAND', 'TELEMETRY'
131
- process_packet(parser, keyword, params, target_name)
131
+ when 'COMMAND'
132
+ finish_packet()
133
+ @current_packet = PacketParser.parse_command(parser, process_target_name, @commands, @warnings)
134
+ @current_cmd_or_tlm = COMMAND
135
+
136
+ when 'TELEMETRY'
137
+ finish_packet()
138
+ @current_packet = PacketParser.parse_telemetry(parser, process_target_name, @telemetry, @latest_data, @warnings)
139
+ @current_cmd_or_tlm = TELEMETRY
132
140
 
133
141
  # Select an existing packet for editing
134
142
  when 'SELECT_COMMAND', 'SELECT_TELEMETRY'
135
143
  usage = "#{keyword} <TARGET NAME> <PACKET NAME>"
136
144
  finish_packet()
137
145
  parser.verify_num_parameters(2, 2, usage)
138
- @current_target_name = target_name
139
- @current_target_name = params[0].upcase if target_name == 'SYSTEM'
140
- @current_packet_name = params[1].upcase
146
+ target_name = process_target_name
147
+ target_name = params[0].upcase if target_name == 'SYSTEM'
148
+ packet_name = params[1].upcase
141
149
 
142
150
  @current_packet = nil
143
151
  if keyword.include?('COMMAND')
144
- @current_cmd_or_tlm = 'Command'
145
- if @commands[@current_target_name]
146
- @current_packet = @commands[@current_target_name][@current_packet_name]
152
+ @current_cmd_or_tlm = COMMAND
153
+ if @commands[target_name]
154
+ @current_packet = @commands[target_name][packet_name]
147
155
  end
148
156
  else
149
- @current_cmd_or_tlm = 'Telemetry'
150
- if @telemetry[@current_target_name]
151
- @current_packet = @telemetry[@current_target_name][@current_packet_name]
157
+ @current_cmd_or_tlm = TELEMETRY
158
+ if @telemetry[target_name]
159
+ @current_packet = @telemetry[target_name][packet_name]
152
160
  end
153
161
  end
154
162
  raise parser.error("Packet not found", usage) unless @current_packet
@@ -199,10 +207,10 @@ module Cosmos
199
207
 
200
208
  # Select an item in the current telemetry packet for editing
201
209
  when 'SELECT_PARAMETER', 'SELECT_ITEM'
202
- if (@current_cmd_or_tlm == 'Command') && (keyword.split('_')[1] == 'ITEM')
210
+ if (@current_cmd_or_tlm == COMMAND) && (keyword.split('_')[1] == 'ITEM')
203
211
  raise parser.error("SELECT_ITEM only applies to telemetry packets")
204
212
  end
205
- if (@current_cmd_or_tlm == 'Telemetry') && (keyword.split('_')[1] == 'PARAMETER')
213
+ if (@current_cmd_or_tlm == TELEMETRY) && (keyword.split('_')[1] == 'PARAMETER')
206
214
  raise parser.error("SELECT_PARAMETER only applies to command packets")
207
215
  end
208
216
  usage = "#{keyword} <#{keyword.split('_')[1]} NAME>"
@@ -211,22 +219,23 @@ module Cosmos
211
219
  begin
212
220
  @current_item = @current_packet.get_item(params[0])
213
221
  rescue # Rescue the default execption to provide a nicer error message
214
- raise parser.error("#{params[0]} not found in #{@current_cmd_or_tlm.downcase} packet #{@current_target_name} #{@current_packet_name}", usage)
222
+ raise parser.error("#{params[0]} not found in #{@current_cmd_or_tlm.downcase} packet #{@current_packet.target_name} #{@current_packet.packet_name}", usage)
215
223
  end
216
224
 
217
225
  # Start a new telemetry item in the current packet
218
226
  when 'ITEM', 'PARAMETER', 'ID_ITEM', 'ID_PARAMETER', 'ARRAY_ITEM', 'ARRAY_PARAMETER', 'APPEND_ITEM', 'APPEND_PARAMETER', 'APPEND_ID_ITEM', 'APPEND_ID_PARAMETER', 'APPEND_ARRAY_ITEM', 'APPEND_ARRAY_PARAMETER'
219
- start_item(parser, keyword, params)
227
+ start_item(parser)
220
228
 
221
229
  # Start the creation of a macro-expanded list of items
222
230
  # This simulates an array of structures of multiple items in the packet by repeating
223
231
  # each item in the list multiple times with a different "index" added to the name.
224
232
  when 'MACRO_APPEND_START'
225
- process_macro_append_start(parser, keyword, params)
233
+ MacroParser.start(parser)
226
234
 
227
235
  # End the creation of a macro-expanded list of items
228
236
  when 'MACRO_APPEND_END'
229
- process_macro_append_end(parser, keyword) # no params for END
237
+ finish_item()
238
+ MacroParser.end(parser, @current_packet)
230
239
 
231
240
  # Allow this packet to be received with less data than the defined length
232
241
  # without generating a warning.
@@ -242,7 +251,7 @@ module Cosmos
242
251
 
243
252
  # Define a processor class that will be called once when a packet is received
244
253
  when 'PROCESSOR'
245
- process_processor(parser, keyword, params)
254
+ ProcessorParser.parse(parser, @current_packet, @current_cmd_or_tlm)
246
255
 
247
256
  when 'DISABLE_MESSAGES'
248
257
  usage = "#{keyword}"
@@ -285,7 +294,7 @@ module Cosmos
285
294
 
286
295
  # Add a state to the current telemety item
287
296
  when 'STATE'
288
- process_state(parser, keyword, params)
297
+ StateParser.parse(parser, @current_packet, @current_cmd_or_tlm, @current_item, @warnings)
289
298
 
290
299
  # Apply a conversion to the current item after it is read to or
291
300
  # written from the packet
@@ -349,16 +358,17 @@ module Cosmos
349
358
 
350
359
  # Define a set of limits for the current telemetry item
351
360
  when 'LIMITS'
352
- process_limits(parser, keyword, params)
361
+ @limits_sets << LimitsParser.parse(parser, @current_packet, @current_cmd_or_tlm, @current_item, @warnings)
362
+ @limits_sets.uniq!
353
363
 
354
364
  # Define a response class that will be called when the limits state of the
355
365
  # current item changes.
356
366
  when 'LIMITS_RESPONSE'
357
- process_limits_response(parser, keyword, params)
367
+ LimitsResponseParser.parse(parser, @current_item, @current_cmd_or_tlm)
358
368
 
359
369
  # Define a printf style formatting string for the current telemetry item
360
370
  when 'FORMAT_STRING'
361
- process_format_string(parser, keyword, params)
371
+ FormatStringParser.parse(parser, @current_item)
362
372
 
363
373
  # Define the units of the current telemetry item
364
374
  when 'UNITS'
@@ -378,7 +388,7 @@ module Cosmos
378
388
  when 'REQUIRED'
379
389
  usage = "REQUIRED"
380
390
  parser.verify_num_parameters(0, 0, usage)
381
- if @current_cmd_or_tlm == 'Command'
391
+ if @current_cmd_or_tlm == COMMAND
382
392
  @current_item.required = true
383
393
  else
384
394
  raise parser.error("#{keyword} only applies to command parameters")
@@ -386,7 +396,7 @@ module Cosmos
386
396
 
387
397
  # Update the mimimum value for the current command parameter
388
398
  when 'MINIMUM_VALUE'
389
- if @current_cmd_or_tlm == 'Telemetry'
399
+ if @current_cmd_or_tlm == TELEMETRY
390
400
  raise parser.error("#{keyword} only applies to command parameters")
391
401
  end
392
402
  usage = "MINIMUM_VALUE <MINIMUM VALUE>"
@@ -397,7 +407,7 @@ module Cosmos
397
407
 
398
408
  # Update the maximum value for the current command parameter
399
409
  when 'MAXIMUM_VALUE'
400
- if @current_cmd_or_tlm == 'Telemetry'
410
+ if @current_cmd_or_tlm == TELEMETRY
401
411
  raise parser.error("#{keyword} only applies to command parameters")
402
412
  end
403
413
  usage = "MAXIMUM_VALUE <MAXIMUM VALUE>"
@@ -408,7 +418,7 @@ module Cosmos
408
418
 
409
419
  # Update the default value for the current command parameter
410
420
  when 'DEFAULT_VALUE'
411
- if @current_cmd_or_tlm == 'Telemetry'
421
+ if @current_cmd_or_tlm == TELEMETRY
412
422
  raise parser.error("#{keyword} only applies to command parameters")
413
423
  end
414
424
  usage = "DEFAULT_VALUE <DEFAULT VALUE>"
@@ -430,597 +440,35 @@ module Cosmos
430
440
  end
431
441
  end
432
442
 
433
- ####################################################
434
- # The following methods process a particular keyword
435
-
436
- def process_macro_append_start(parser, keyword, params)
437
- @macro_append.building = true
438
-
439
- usage = '#{keyword} <FIRST INDEX> <LAST INDEX> [NAME FORMAT]'
440
- parser.verify_num_parameters(2, 3, usage)
441
-
442
- # Store the params
443
- first_index = params[0].to_i
444
- last_index = params[1].to_i
445
- @macro_append.indices = [first_index, last_index].sort
446
- @macro_append.indices = (@macro_append.indices[0]..@macro_append.indices[1]).to_a
447
- @macro_append.indices.reverse! if first_index > last_index
448
- @macro_append.format = params[2] ? params[2] : '%s%d'
449
- spos = @macro_append.format.index(/%\d*s/)
450
- dpos = @macro_append.format.index(/%\d*d/)
451
- raise parser.error("Invalid NAME FORMAT (#{@macro_append.format}) for MACRO_APPEND_START", usage) unless spos and dpos
452
- if spos < dpos
453
- @macro_append.format_order = 'sd'
454
- else
455
- @macro_append.format_order = 'ds'
456
- end
457
- end
458
-
459
- def process_macro_append_end(parser, keyword)
460
- update_cache = false
461
- finish_item()
462
- parser.verify_num_parameters(0, 0, keyword)
463
- raise parser.error("Missing MACRO_APPEND_START before this config.line.", keyword) unless @macro_append.building
464
- raise parser.error("No items appended in MACRO_APPEND list", keyword) unless @macro_append.list.length > 0
465
-
466
- # Get first index, remove from array
467
- first = @macro_append.indices.shift
468
-
469
- # Rename the items in the list using the first index
470
- items = @current_packet.items
471
- @macro_append.list.each do |name|
472
- item = items[name]
473
- items.delete name
474
- if @macro_append.format_order == 'sd'
475
- first_name = sprintf(@macro_append.format, name, first)
476
- else
477
- first_name = sprintf(@macro_append.format, first, name)
478
- end
479
- item.name = first_name
480
- items[first_name] = item
481
- end
482
-
483
- # Append multiple copies of the items in the list
484
- @macro_append.indices.each do |index|
485
- @macro_append.list.each do |name|
486
- if @macro_append.format_order == 'sd'
487
- first_name = sprintf(@macro_append.format, name, first)
488
- this_name = sprintf(@macro_append.format, name, index)
489
- else
490
- first_name = sprintf(@macro_append.format, first, name)
491
- this_name = sprintf(@macro_append.format, index, name)
492
- end
493
- first_item = items[first_name]
494
- format_string = nil
495
- format_string = first_item.format_string if first_item.format_string
496
- this_item = @current_packet.append_item(this_name,
497
- first_item.bit_size,
498
- first_item.data_type,
499
- first_item.array_size,
500
- first_item.endianness,
501
- first_item.overflow,
502
- format_string,
503
- first_item.read_conversion,
504
- first_item.write_conversion,
505
- first_item.id_value)
506
- this_item.states = first_item.states if first_item.states
507
- this_item.description = first_item.description if first_item.description
508
- this_item.units_full = first_item.units_full if first_item.units_full
509
- this_item.units = first_item.units if first_item.units
510
- this_item.default = first_item.default
511
- this_item.range = first_item.range if first_item.range
512
- this_item.required = first_item.required
513
- this_item.hazardous = first_item.hazardous
514
- if first_item.state_colors
515
- this_item.state_colors = first_item.state_colors
516
- update_cache = true
517
- end
518
- if first_item.limits
519
- this_item.limits = first_item.limits
520
- update_cache = true
521
- end
522
- end
523
- end
524
- @current_packet.update_limits_items_cache if update_cache
525
-
526
- @macro_append.building = false
527
- @macro_append.indices = []
528
- @macro_append.list = []
529
- end
530
-
531
- def process_state(parser, keyword, params)
532
- if @current_cmd_or_tlm == 'Command'
533
- usage = "#{keyword} <STATE NAME> <STATE VALUE> <HAZARDOUS (Optional)> <Hazardous Description (Optional)>"
534
- parser.verify_num_parameters(2, 4, usage)
535
- else
536
- usage = "#{keyword} <STATE NAME> <STATE VALUE> <COLOR: GREEN/YELLOW/RED (Optional)>"
537
- parser.verify_num_parameters(2, 3, usage)
538
- end
539
- @current_item.states ||= {}
540
- if @current_item.states[params[0].upcase]
541
- msg = "Duplicate state defined on line #{parser.line_number}: #{parser.line}"
542
- Logger.instance.warn(msg)
543
- @warnings << msg
544
- end
545
- if @current_item.data_type == :STRING or @current_item.data_type == :BLOCK
546
- @current_item.states[params[0].upcase] = params[1]
547
- else
548
- @current_item.states[params[0].upcase] = params[1].convert_to_value
549
- end
550
- if params[2]
551
- if @current_cmd_or_tlm == 'Command'
552
- if params[2].upcase == 'HAZARDOUS'
553
- @current_item.hazardous ||= {}
554
- if params[3]
555
- @current_item.hazardous[params[0].upcase] = params[3]
556
- else
557
- @current_item.hazardous[params[0].upcase] = ""
558
- end
559
- else
560
- raise parser.error("HAZARDOUS expected as third parameter for this line.", usage)
561
- end
562
- else
563
- if params[2]
564
- color = params[2].upcase.to_sym
565
- unless PacketItem::STATE_COLORS.include? color
566
- raise parser.error("Invalid state color #{color}. Must be one of #{PacketItem::STATE_COLORS.join(' ')}.", usage)
567
- end
568
- @current_item.limits ||= Limits.new
569
- @current_item.limits.enabled = true
570
- @current_item.state_colors ||= {}
571
- @current_item.state_colors[params[0].upcase] = color
572
- @current_packet.update_limits_items_cache
573
- end
574
- end
575
- end
576
- end
577
-
578
- def process_limits(parser, keyword, params)
579
- if @current_cmd_or_tlm == 'Command'
580
- raise parser.error("#{keyword} only applies to telemetry items")
581
- end
582
- usage = "#{keyword} <LIMITS SET> <PERSISTENCE> <ENABLED/DISABLED> <RED LOW LIMIT> <YELLOW LOW LIMIT> <YELLOW HIGH LIMIT> <RED HIGH LIMIT> <GREEN LOW LIMIT (Optional)> <GREEN HIGH LIMIT (Optional)>"
583
- parser.verify_num_parameters(7, 9, usage)
584
-
585
- begin
586
- persistence = Integer(params[1])
587
- red_low = Float(params[3])
588
- yellow_low = Float(params[4])
589
- yellow_high = Float(params[5])
590
- red_high = Float(params[6])
591
- rescue
592
- raise parser.error("Invalid persistence or limits values. Ensure persistence is an integer. Limits can be integers or floats.", usage)
593
- end
594
-
595
- enabled = params[2].upcase
596
- if enabled != 'ENABLED' and enabled != 'DISABLED'
597
- raise parser.error("Initial state must be ENABLED or DISABLED.", usage)
598
- end
599
-
600
- # Verify valid limits are specified
601
- if (red_low > yellow_low) or (yellow_low >= yellow_high) or (yellow_high > red_high)
602
- raise parser.error("Invalid limits specified. Ensure yellow limits are within red limits.", usage)
603
- end
604
- if params.length != 7
605
- begin
606
- green_low = Float(params[7])
607
- green_high = Float(params[8])
608
- rescue
609
- raise parser.error("Invalid green limits values. Limits can be integers or floats.", usage)
610
- end
611
-
612
- if (yellow_low > green_low) or (green_low >= green_high) or (green_high > yellow_high)
613
- raise parser.error("Invalid limits specified. Ensure green limits are within yellow limits.", usage)
614
- end
615
- end
616
-
617
- limits_set = params[0].upcase.to_sym
618
- @limits_sets << limits_set
619
- @limits_sets.uniq!
620
- # Initialize the limits values. Values must be initialized with a :DEFAULT key
621
- if !@current_item.limits.values
622
- if limits_set == :DEFAULT
623
- @current_item.limits.values = {:DEFAULT => []}
624
- else
625
- raise parser.error("DEFAULT limits must be defined for #{@current_packet.target_name} #{@current_packet.packet_name} #{@current_item.name} before setting limits set #{limits_set}")
626
- end
627
- end
628
- if limits_set != :DEFAULT
629
- msg = nil
630
- if (enabled == 'ENABLED' and @current_item.limits.enabled != true) or (enabled != 'ENABLED' and @current_item.limits.enabled != false)
631
- msg = "#{@current_cmd_or_tlm} Item #{@current_target_name} #{@current_packet_name} #{@current_item.name} #{limits_set} limits enable setting conflict with DEFAULT"
632
- end
633
- if @current_item.limits.persistence_setting != persistence
634
- msg = "#{@current_cmd_or_tlm} Item #{@current_target_name} #{@current_packet_name} #{@current_item.name} #{limits_set} limits persistence setting conflict with DEFAULT"
635
- end
636
- if msg
637
- Logger.instance.warn msg
638
- @warnings << msg
639
- end
640
- end
641
- @current_item.limits.enabled = true if enabled == 'ENABLED'
642
- values = @current_item.limits.values
643
- if params.length == 7
644
- values[limits_set] = [red_low, yellow_low, yellow_high, red_high]
645
- else
646
- values[limits_set] = [red_low, yellow_low, yellow_high, red_high, green_low, green_high]
647
- end
648
- @current_item.limits.values = values
649
- @current_item.limits.persistence_setting = persistence
650
- @current_item.limits.persistence_count = 0
651
- @current_packet.update_limits_items_cache
652
- end
653
-
654
- def process_limits_response(parser, keyword, params)
655
- if @current_cmd_or_tlm == 'Command'
656
- raise parser.error("#{keyword} only applies to telemetry items")
657
- end
658
- usage = "#{keyword} <RESPONSE CLASS FILENAME> <RESPONSE SPECIFIC OPTIONS>"
659
- parser.verify_num_parameters(1, nil, usage)
660
-
661
- begin
662
- # require should be performed in target.txt
663
- klass = params[0].filename_to_class_name.to_class
664
- raise parser.error("#{params[0].filename_to_class_name} class not found. Did you require the file in target.txt?", usage) unless klass
665
- if params[1]
666
- @current_item.limits.response = klass.new(*params[1..(params.length - 1)])
667
- else
668
- @current_item.limits.response = klass.new
669
- end
670
- rescue Exception => err
671
- raise parser.error(err, usage)
672
- end
673
- end
674
-
675
- def process_processor(parser, keyword, params)
676
- if @current_cmd_or_tlm == 'Command'
677
- raise parser.error("#{keyword} only applies to telemetry packets")
678
- end
679
- usage = "#{keyword} <PROCESSOR NAME> <PROCESSOR CLASS FILENAME> <PROCESSOR SPECIFIC OPTIONS>"
680
- parser.verify_num_parameters(2, nil, usage)
681
-
682
- begin
683
- # require should be performed in target.txt
684
- klass = params[1].filename_to_class_name.to_class
685
- raise parser.error("#{params[1].filename_to_class_name} class not found. Did you require the file in target.txt?", usage) unless klass
686
- if params[2]
687
- processor = klass.new(*params[2..(params.length - 1)])
688
- else
689
- processor = klass.new
690
- end
691
- raise ArgumentError, "processor must be a Cosmos::Processor but is a #{processor.class}" unless Cosmos::Processor === processor
692
- processor.name = params[0]
693
- @current_packet.processors[params[0].to_s.upcase] = processor
694
- rescue Exception => err
695
- raise parser.error(err, usage)
696
- end
697
- end
698
-
699
- def process_format_string(parser, keyword, params)
700
- usage = "#{keyword} <PRINTF STYLE STRING>"
701
- parser.verify_num_parameters(1, 1, usage)
702
- @current_item.format_string = params[0]
703
- unless @current_item.read_conversion
704
- # Check format string as long as a read conversion has not been defined
705
- begin
706
- case @current_item.data_type
707
- when :INT, :UINT
708
- sprintf(@current_item.format_string, 0)
709
- when :FLOAT
710
- sprintf(@current_item.format_string, 0.0)
711
- when :STRING, :BLOCK
712
- sprintf(@current_item.format_string, 'Hello')
713
- else
714
- # Nothing to do
715
- end
716
- rescue Exception
717
- raise parser.error("Invalid #{keyword} specified for type #{@current_item.data_type}: #{params[0]}", usage)
718
- end
719
- end
720
- end
721
-
722
- def process_packet(parser, keyword, params, target_name)
723
- finish_packet()
724
-
725
- usage = "#{keyword} <TARGET NAME> <PACKET NAME> <ENDIANNESS: BIG_ENDIAN/LITTLE_ENDIAN> <DESCRIPTION (Optional)>"
726
- parser.verify_num_parameters(3, 4, usage)
727
- target_name = params[0].to_s.upcase if target_name == 'SYSTEM'
728
- packet_name = params[1].to_s.upcase
729
- endianness = params[2].to_s.upcase.intern
730
- description = params[3].to_s
731
- if endianness != :BIG_ENDIAN and endianness != :LITTLE_ENDIAN
732
- raise parser.error("Invalid endianness #{params[2]}. Must be BIG_ENDIAN or LITTLE_ENDIAN.", usage)
733
- end
734
-
735
- @current_target_name = target_name
736
- @current_packet_name = packet_name
737
- @current_cmd_or_tlm = keyword.capitalize
738
-
739
- # Be sure there is not already a packet by this name
740
- if @current_cmd_or_tlm == 'Command'
741
- if @commands[@current_target_name]
742
- if @commands[@current_target_name][@current_packet_name]
743
- msg = "#{@current_cmd_or_tlm} Packet #{@current_target_name} #{@current_packet_name} redefined."
744
- Logger.instance.warn msg
745
- @warnings << msg
746
- end
747
- end
748
- else
749
- if @telemetry[@current_target_name]
750
- if @telemetry[@current_target_name][@current_packet_name]
751
- msg = "#{@current_cmd_or_tlm} Packet #{@current_target_name} #{@current_packet_name} redefined."
752
- Logger.instance.warn msg
753
- @warnings << msg
754
- end
755
- end
756
- end
757
-
758
- @current_packet = Packet.new(@current_target_name, @current_packet_name, endianness, description)
759
-
760
- # Add received time packet items
761
- if @current_cmd_or_tlm == 'Telemetry'
762
- item = @current_packet.define_item('RECEIVED_TIMESECONDS', 0, 0, :DERIVED, nil, @current_packet.default_endianness, :ERROR, '%0.6f', ReceivedTimeSecondsConversion.new)
763
- item.description = 'COSMOS Received Time (UTC, Floating point, Unix epoch)'
764
- item = @current_packet.define_item('RECEIVED_TIMEFORMATTED', 0, 0, :DERIVED, nil, @current_packet.default_endianness, :ERROR, nil, ReceivedTimeFormattedConversion.new)
765
- item.description = 'COSMOS Received Time (Local time zone, Formatted string)'
766
- item = @current_packet.define_item('RECEIVED_COUNT', 0, 0, :DERIVED, nil, @current_packet.default_endianness, :ERROR, nil, ReceivedCountConversion.new)
767
- item.description = 'COSMOS packet received count'
768
-
769
- unless @telemetry[@current_target_name]
770
- @telemetry[@current_target_name] = {}
771
- @latest_data[@current_target_name] = {}
772
- end
773
- else
774
- @commands[@current_target_name] ||= {}
775
- end
776
- end
777
-
778
443
  # Add current packet into hash if it exists
779
444
  def finish_packet
780
445
  finish_item()
781
446
  if @current_packet
782
- # Review bit offset to look for overlapping definitions
783
- # This will allow gaps in the packet, but not allow the same bits to be
784
- # used for multiple variables.
785
- expected_next_offset = nil
786
- previous_item = nil
787
- @current_packet.sorted_items.each do |item|
788
- if expected_next_offset and item.bit_offset < expected_next_offset
789
- msg = "Bit definition overlap at bit offset #{item.bit_offset} for #{@current_cmd_or_tlm} packet #{@current_target_name} #{@current_packet_name} items #{item.name} and #{previous_item.name}"
790
- Logger.instance.warn(msg)
791
- @warnings << msg
792
- end
793
- if item.array_size
794
- if item.array_size > 0
795
- expected_next_offset = item.bit_offset + item.array_size
796
- else
797
- expected_next_offset = item.array_size
798
- end
799
- else
800
- expected_next_offset = nil
801
- if item.bit_offset > 0
802
- # Handle little-endian bit fields
803
- byte_aligned = ((item.bit_offset % 8) == 0)
804
- if item.endianness == :LITTLE_ENDIAN and (item.data_type == :INT or item.data_type == :UINT) and !(byte_aligned and (item.bit_size == 8 or item.bit_size == 16 or item.bit_size == 32 or item.bit_size == 64))
805
- # Bitoffset always refers to the most significant bit of a bitfield
806
- bits_remaining_in_last_byte = 8 - (item.bit_offset % 8)
807
- if item.bit_size > bits_remaining_in_last_byte
808
- expected_next_offset = item.bit_offset + bits_remaining_in_last_byte
809
- end
810
- end
811
- end
812
- unless expected_next_offset
813
- if item.bit_size > 0
814
- expected_next_offset = item.bit_offset + item.bit_size
815
- else
816
- expected_next_offset = item.bit_size
817
- end
818
- end
819
- end
820
- previous_item = item
821
-
822
- # Check command default and range data types if no write conversion is present
823
- item.check_default_and_range_data_types if @current_cmd_or_tlm == 'Command'
824
- end
447
+ @warnings += @current_packet.check_bit_offsets
825
448
 
826
- # commit packet to memory
827
- if @current_cmd_or_tlm == 'Command'
828
- @commands[@current_target_name][@current_packet_name] = @current_packet
449
+ if @current_cmd_or_tlm == COMMAND
450
+ PacketParser.check_item_data_types(@current_packet)
451
+ @commands[@current_packet.target_name][@current_packet.packet_name] = @current_packet
829
452
  else
830
- @telemetry[@current_target_name][@current_packet_name] = @current_packet
453
+ @telemetry[@current_packet.target_name][@current_packet.packet_name] = @current_packet
831
454
  end
832
455
  @current_packet = nil
833
456
  @current_item = nil
834
457
  end
835
458
  end
836
459
 
837
- # There are many different usages of the ITEM keword so parse the keyword
838
- # and parameters to generate the correct usage information.
839
- def generate_item_usage(keyword, params)
840
- usage = "#{keyword} <ITEM NAME> "
841
- usage << "<BIT OFFSET> " unless keyword.include?("APPEND")
842
- if keyword.include?("ARRAY")
843
- usage << "<ARRAY ITEM BIT SIZE> "
844
- else
845
- usage << "<BIT SIZE> "
846
- end
847
- if keyword.include?("PARAMETER")
848
- if keyword.include?("ARRAY")
849
- usage << "<TYPE: INT/UINT/FLOAT/STRING/BLOCK> "
850
- else
851
- if keyword.include?("APPEND")
852
- data_type = params[2].upcase.to_sym
853
- else
854
- data_type = params[3].upcase.to_sym
855
- end
856
- if data_type == :STRING or data_type == :BLOCK
857
- if keyword.include?("ID")
858
- usage << "<TYPE: STRING/BLOCK> "
859
- else
860
- usage << "<TYPE: STRING/BLOCK> <DEFAULT VALUE>"
861
- end
862
- else
863
- if keyword.include?("ID")
864
- usage << "<TYPE: INT/UINT/FLOAT> <MIN VALUE> <MAX VALUE> "
865
- else
866
- usage << "<TYPE: INT/UINT/FLOAT/DERIVED> <MIN VALUE> <MAX VALUE> <DEFAULT VALUE>"
867
- end
868
- end
869
- end
870
- else
871
- usage << "<TYPE: INT/UINT/FLOAT/STRING/BLOCK/DERIVED> "
872
- end
873
- usage << "<TOTAL ARRAY BIT SIZE> " if keyword.include?("ARRAY")
874
- if keyword.include?("ID")
875
- if keyword.include?("PARAMETER")
876
- usage << "<DEFAULT AND ID VALUE> "
877
- else
878
- usage << "<ID VALUE> "
879
- end
880
- end
881
- usage << "<DESCRIPTION (Optional)> <ENDIANNESS (Optional)>"
882
- return usage
883
- end
884
-
885
- def start_item(parser, keyword, params)
460
+ def start_item(parser)
886
461
  finish_item()
887
-
888
- usage = generate_item_usage(keyword, params)
889
- max_options = usage.count("<")
890
- parser.verify_num_parameters(max_options-2, max_options, usage)
891
- begin
892
- if params[max_options-1]
893
- endianness = params[max_options-1].to_s.upcase.intern
894
- if endianness != :BIG_ENDIAN and endianness != :LITTLE_ENDIAN
895
- raise parser.error("Invalid endianness #{params[2]}. Must be BIG_ENDIAN or LITTLE_ENDIAN.", usage)
896
- end
897
- else
898
- endianness = @current_packet.default_endianness
899
- end
900
-
901
- case keyword
902
- when /ITEM/
903
- raise parser.error("ITEM types are only valid with TELEMETRY", usage) if @current_cmd_or_tlm == 'Command'
904
- # If this is an APPEND we don't have a bit offset so the index
905
- # into the parameters changes
906
- index = (keyword =~ /APPEND/) ? 3 : 4
907
- id_value = (keyword =~ /ID_ITEM/) ? params[index] : nil
908
- array_size = (keyword =~ /ARRAY_ITEM/) ? Integer(params[index]) : nil
909
- case keyword
910
- when 'ITEM', 'ID_ITEM', 'ARRAY_ITEM'
911
- @current_item = @current_packet.define_item(params[0], # name
912
- Integer(params[1]), # bit offset
913
- Integer(params[2]), # bit size
914
- params[3].upcase.to_sym, # data_type
915
- array_size, # array size
916
- endianness, # endianness
917
- :ERROR, # overflow
918
- nil, # format string
919
- nil, # read conversion
920
- nil, # write conversion
921
- id_value) # id value
922
- when 'APPEND_ITEM', 'APPEND_ID_ITEM', 'APPEND_ARRAY_ITEM'
923
- @current_item = @current_packet.append_item(params[0], # name
924
- Integer(params[1]), # bit size
925
- params[2].upcase.to_sym, # data_type
926
- array_size, # array size
927
- endianness, # endianness
928
- :ERROR, # overflow
929
- nil, # format string
930
- nil, # read conversion
931
- nil, # write conversion
932
- id_value) # id value
933
- end
934
- when 'PARAMETER', 'ID_PARAMETER', 'ARRAY_PARAMETER'
935
- raise parser.error("PARAMETER types are only valid with COMMAND", usage) if @current_cmd_or_tlm == 'Telemetry'
936
- data_type = params[3].upcase.to_sym
937
- id_value = nil
938
- if keyword == 'ID_PARAMETER'
939
- if data_type == :DERIVED
940
- raise "DERIVED data type not allowed"
941
- elsif data_type == :STRING or data_type == :BLOCK
942
- id_value = params[4]
943
- else
944
- id_value = params[6]
945
- end
946
- end
947
- array_size = (keyword == 'ARRAY_PARAMETER') ? Integer(params[4]) : nil
948
- @current_item = @current_packet.define_item(params[0], # name
949
- Integer(params[1]), # bit offset
950
- Integer(params[2]), # bit size
951
- data_type, # data_type
952
- array_size, # array size
953
- endianness, # endianness
954
- :ERROR, # overflow
955
- nil, # format string
956
- nil, # read conversion
957
- nil, # write conversion
958
- id_value) # id value
959
- if keyword == 'ARRAY_PARAMETER'
960
- @current_item.default = []
961
- else
962
- if data_type == :STRING or data_type == :BLOCK
963
- @current_item.default = params[4]
964
- else
965
- @current_item.range =
966
- (ConfigParser.handle_defined_constants(params[4].convert_to_value))..(ConfigParser.handle_defined_constants(params[5].convert_to_value))
967
- @current_item.default = ConfigParser.handle_defined_constants(params[6].convert_to_value)
968
- end
969
- end
970
- when 'APPEND_PARAMETER', 'APPEND_ID_PARAMETER', 'APPEND_ARRAY_PARAMETER'
971
- raise parser.error("PARAMETER types are only valid with COMMAND", usage) if @current_cmd_or_tlm == 'Telemetry'
972
- data_type = params[2].upcase.to_sym
973
- id_value = nil
974
- if keyword == 'APPEND_ID_PARAMETER'
975
- if data_type == :DERIVED
976
- raise "DERIVED data type not allowed"
977
- elsif data_type == :STRING or data_type == :BLOCK
978
- id_value = params[3]
979
- else
980
- id_value = params[5]
981
- end
982
- end
983
- array_size = (keyword == 'APPEND_ARRAY_PARAMETER') ? Integer(params[3]) : nil
984
- @current_item = @current_packet.append_item(params[0], # name
985
- Integer(params[1]), # bit size
986
- data_type, # data_type
987
- array_size, # array size
988
- endianness, # endianness
989
- :ERROR, # overflow
990
- nil, # format string
991
- nil, # read conversion
992
- nil, # write conversion
993
- id_value) # id value
994
- if keyword == 'APPEND_ARRAY_PARAMETER'
995
- @current_item.default = []
996
- else
997
- if data_type == :STRING or data_type == :BLOCK
998
- @current_item.default = params[3]
999
- else
1000
- @current_item.range =
1001
- (ConfigParser.handle_defined_constants(params[3].convert_to_value))..(ConfigParser.handle_defined_constants(params[4].convert_to_value))
1002
- @current_item.default = ConfigParser.handle_defined_constants(params[5].convert_to_value)
1003
- end
1004
- end
1005
- end
1006
- @current_item.description = params[max_options-2] if params[max_options-2]
1007
-
1008
- if keyword.include?('APPEND') && @macro_append.building
1009
- @macro_append.list << params[0].upcase
1010
- end
1011
-
1012
- # Rescue the item processing since they could also throw configuration errors
1013
- rescue => err
1014
- raise parser.error(err, usage)
1015
- end
462
+ @current_item = PacketItemParser.parse(parser, @current_packet, @current_cmd_or_tlm)
463
+ MacroParser.new_item()
1016
464
  end
1017
465
 
1018
- # Finish updating item in packet
466
+ # Finish updating packet item
1019
467
  def finish_item
1020
468
  if @current_item
1021
469
  @current_packet.set_item(@current_item)
1022
- if @current_cmd_or_tlm == 'Telemetry'
1023
- target_latest_data = @latest_data[@current_target_name]
470
+ if @current_cmd_or_tlm == TELEMETRY
471
+ target_latest_data = @latest_data[@current_packet.target_name]
1024
472
  target_latest_data[@current_item.name] ||= []
1025
473
  latest_data_packets = target_latest_data[@current_item.name]
1026
474
  latest_data_packets << @current_packet unless latest_data_packets.include?(@current_packet)