cosmos 4.0.3 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -5
  3. data/Manifest.txt +11 -1
  4. data/README.md +3 -2
  5. data/Rakefile +18 -4
  6. data/appveyor.yml +19 -0
  7. data/cosmos.gemspec +14 -3
  8. data/data/config/cmd_tlm_server.yaml +3 -0
  9. data/data/crc.txt +63 -60
  10. data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
  11. data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
  12. data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
  13. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
  14. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
  15. data/demo/lib/cmd_sequence_exporter.rb +52 -0
  16. data/demo/lib/example_background_task.rb +1 -0
  17. data/demo/procedures/replay_test.rb +32 -0
  18. data/ext/cosmos/ext/structure/structure.c +39 -3
  19. data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
  20. data/install/config/tools/launcher/launcher.txt +2 -0
  21. data/lib/cosmos/config/config_parser.rb +2 -0
  22. data/lib/cosmos/core_ext/io.rb +89 -60
  23. data/lib/cosmos/gui/qt.rb +5 -8
  24. data/lib/cosmos/gui/qt_tool.rb +8 -8
  25. data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
  26. data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
  27. data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
  28. data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
  29. data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
  30. data/lib/cosmos/interfaces/udp_interface.rb +27 -14
  31. data/lib/cosmos/io/buffered_file.rb +0 -1
  32. data/lib/cosmos/io/json_drb.rb +134 -214
  33. data/lib/cosmos/io/json_drb_object.rb +22 -61
  34. data/lib/cosmos/io/json_drb_rack.rb +79 -0
  35. data/lib/cosmos/io/json_rpc.rb +27 -0
  36. data/lib/cosmos/io/udp_sockets.rb +102 -58
  37. data/lib/cosmos/packets/commands.rb +1 -1
  38. data/lib/cosmos/packets/structure.rb +1 -1
  39. data/lib/cosmos/packets/structure_item.rb +37 -5
  40. data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
  41. data/lib/cosmos/script/replay.rb +60 -0
  42. data/lib/cosmos/script/script.rb +20 -2
  43. data/lib/cosmos/script/scripting.rb +9 -9
  44. data/lib/cosmos/script/tools.rb +14 -0
  45. data/lib/cosmos/system/system.rb +185 -92
  46. data/lib/cosmos/system/target.rb +1 -1
  47. data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
  48. data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
  49. data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
  50. data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
  51. data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
  52. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
  53. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
  54. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
  55. data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
  56. data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
  57. data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
  58. data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
  59. data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
  60. data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
  61. data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
  62. data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
  63. data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
  64. data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
  65. data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
  66. data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
  67. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
  68. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
  69. data/lib/cosmos/tools/replay/replay.rb +5 -505
  70. data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
  71. data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
  72. data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
  73. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
  74. data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
  75. data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
  76. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
  77. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
  78. data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
  79. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
  80. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
  81. data/lib/cosmos/top_level.rb +34 -24
  82. data/lib/cosmos/utilities/csv.rb +60 -8
  83. data/lib/cosmos/version.rb +5 -5
  84. data/spec/config/config_parser_spec.rb +10 -1
  85. data/spec/core_ext/socket_spec.rb +4 -2
  86. data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
  87. data/spec/install/config/data/data.txt +1 -0
  88. data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
  89. data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
  90. data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
  91. data/spec/interfaces/serial_interface_spec.rb +1 -1
  92. data/spec/interfaces/udp_interface_spec.rb +14 -0
  93. data/spec/io/buffered_file_spec.rb +37 -0
  94. data/spec/io/json_drb_object_spec.rb +2 -15
  95. data/spec/io/json_drb_spec.rb +61 -121
  96. data/spec/io/udp_sockets_spec.rb +42 -2
  97. data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
  98. data/spec/packets/binary_accessor_spec.rb +1 -1
  99. data/spec/packets/packet_item_spec.rb +1 -1
  100. data/spec/packets/structure_item_spec.rb +5 -6
  101. data/spec/script/cmd_tlm_server_spec.rb +39 -4
  102. data/spec/script/commands_disconnect_spec.rb +1 -1
  103. data/spec/script/commands_spec.rb +2 -1
  104. data/spec/script/scripting_spec.rb +18 -3
  105. data/spec/script/telemetry_spec.rb +5 -0
  106. data/spec/spec_helper.rb +43 -26
  107. data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
  108. data/spec/system/system_spec.rb +11 -9
  109. data/spec/system/target_spec.rb +3 -0
  110. data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
  111. data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
  112. data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
  113. data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
  114. data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
  115. data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
  116. data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
  117. data/spec/tools/launcher/launcher_config_spec.rb +1 -1
  118. data/spec/tools/table_manager/table_item_spec.rb +1 -1
  119. data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
  120. data/spec/top_level/top_level_spec.rb +151 -3
  121. data/spec/utilities/csv_spec.rb +24 -5
  122. metadata +61 -9
  123. data/lib/cosmos/tools/replay/replay_server.rb +0 -91
@@ -14,21 +14,33 @@ require 'cosmos/gui/qt'
14
14
  module Cosmos
15
15
  # Implements the status tab in the Command and Telemetry Server GUI
16
16
  class StatusTab
17
+ def initialize(tab_widget)
18
+ @widget = nil
19
+ reset()
20
+ @scroll = Qt::ScrollArea.new
21
+ tab_widget.addTab(@scroll, "Status")
22
+ end
23
+
24
+ def reset
25
+ Qt.execute_in_main_thread(true) do
26
+ @widget.destroy if @widget
27
+ @widget = nil
28
+ end
29
+ end
30
+
17
31
  # Create the status tab and add it to the tab_widget
18
32
  # @param tab_widget [Qt::TabWidget] The tab widget to add the tab to
19
- def populate(tab_widget)
20
- scroll = Qt::ScrollArea.new
21
- widget = Qt::Widget.new
22
- layout = Qt::VBoxLayout.new(widget)
33
+ def populate
34
+ reset()
35
+ @widget = Qt::Widget.new
36
+ layout = Qt::VBoxLayout.new(@widget)
23
37
 
24
38
  populate_limits_status(layout)
25
39
  populate_api_status(layout)
26
40
  populate_system_status(layout)
27
41
  populate_background_status(layout)
28
42
 
29
- # Set the scroll area widget last now that all the items have been layed out
30
- scroll.setWidget(widget)
31
- tab_widget.addTab(scroll, "Status")
43
+ @scroll.setWidget(@widget)
32
44
  end
33
45
 
34
46
  # Update the status tab in the GUI
@@ -85,7 +97,11 @@ module Cosmos
85
97
  @api_table.setColumnCount(6)
86
98
  @api_table.setHorizontalHeaderLabels(["Port", "Num Clients", "Requests", "Requests/Sec", "Avg Request Time", "Estimated Utilization"])
87
99
 
88
- @api_table.setItem(0, 0, Qt::TableWidgetItem.new(Qt::Object.tr(System.ports['CTS_API'].to_s)))
100
+ if CmdTlmServer.mode == :CMD_TLM_SERVER
101
+ @api_table.setItem(0, 0, Qt::TableWidgetItem.new(Qt::Object.tr(System.ports['CTS_API'].to_s)))
102
+ else
103
+ @api_table.setItem(0, 0, Qt::TableWidgetItem.new(Qt::Object.tr(System.ports['REPLAY_API'].to_s)))
104
+ end
89
105
  item0 = Qt::TableWidgetItem.new(Qt::Object.tr(CmdTlmServer.json_drb.num_clients.to_s))
90
106
  item0.setTextAlignment(Qt::AlignCenter)
91
107
  @api_table.setItem(0, 1, item0)
@@ -15,16 +15,29 @@ module Cosmos
15
15
 
16
16
  # Implements the targets tab in the Command and Telemetry Server GUI
17
17
  class TargetsTab
18
+ def initialize(tab_widget)
19
+ @widget = nil
20
+ reset()
21
+ @scroll = Qt::ScrollArea.new
22
+ tab_widget.addTab(@scroll, "Targets")
23
+ end
24
+
25
+ def reset
26
+ Qt.execute_in_main_thread(true) do
27
+ @widget.destroy if @widget
28
+ @widget = nil
29
+ end
30
+ end
18
31
 
19
32
  # Create the targets tab and add it to the tab_widget
20
33
  #
21
34
  # @param tab_widget [Qt::TabWidget] The tab widget to add the tab to
22
- def populate(tab_widget)
35
+ def populate
36
+ reset()
23
37
  num_targets = System.targets.length
24
38
  if num_targets > 0
25
- scroll = Qt::ScrollArea.new
26
- widget = Qt::Widget.new
27
- layout = Qt::VBoxLayout.new(widget)
39
+ @widget = Qt::Widget.new
40
+ layout = Qt::VBoxLayout.new(@widget)
28
41
  # Since the layout will be inside a scroll area make sure it respects the sizes we set
29
42
  layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
30
43
 
@@ -39,8 +52,7 @@ module Cosmos
39
52
  @targets_table.displayFullSize
40
53
 
41
54
  layout.addWidget(@targets_table)
42
- scroll.setWidget(widget)
43
- tab_widget.addTab(scroll, "Targets")
55
+ @scroll.setWidget(@widget)
44
56
  end
45
57
  end
46
58
 
@@ -24,6 +24,7 @@ module Cosmos
24
24
  @task_delay = Float(task_delay)
25
25
  @name = "Limits Groups"
26
26
  @groups = get_limits_groups()
27
+ @sleeper = Sleeper.new
27
28
  # Initialize all the group names as instance variables
28
29
  @groups.each {|group| self.instance_variable_set("@#{group.downcase}", nil) }
29
30
  end
@@ -80,9 +81,9 @@ module Cosmos
80
81
 
81
82
  def call
82
83
  @status = "Starting the LimitsGroupsBackgroundTask"
83
- check_methods = find_check_methods()
84
- sleep @initial_delay
85
84
  @sleeper = Sleeper.new
85
+ check_methods = find_check_methods()
86
+ return if @sleeper.sleep(@initial_delay)
86
87
  loop do
87
88
  start = Time.now
88
89
  check_methods.each {|method| self.send(method.intern) }
@@ -90,13 +91,13 @@ module Cosmos
90
91
  @status = "#{now.formatted}: Checking groups took #{now - start}s"
91
92
  sleep_time = @task_delay - (now - start)
92
93
  sleep_time = 0 if sleep_time < 0
93
- broken = @sleeper.sleep(sleep_time)
94
- break if broken
94
+ return if @sleeper.sleep(sleep_time)
95
95
  end
96
96
  end
97
97
 
98
98
  def stop
99
99
  @sleeper.cancel
100
+ @status = "Stopped at #{Time.now.sys.formatted}"
100
101
  end
101
102
 
102
103
  protected
@@ -0,0 +1,375 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2017 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+
11
+ module Cosmos
12
+
13
+ # Handles logic for the Replay mode
14
+ class ReplayBackend
15
+
16
+ # The number of bytes to print when an UNKNOWN packet is received
17
+ UNKNOWN_BYTES_TO_PRINT = 36
18
+
19
+ attr_accessor :log_directory
20
+ attr_accessor :log_filename
21
+ attr_accessor :packet_log_reader
22
+ attr_accessor :config_change_callback
23
+
24
+ # @param cmd_tlm_server_config [CmdTlmServerConfig]
25
+ def initialize(cmd_tlm_server_config)
26
+ @config = cmd_tlm_server_config
27
+ reset()
28
+ @config_change_callback = nil
29
+ end
30
+
31
+ # Reset internal state
32
+ def reset
33
+ @cancel = false
34
+ @playback_delay = 0.0
35
+ @default_packet_log_reader = System.default_packet_log_reader.new(*System.default_packet_log_reader_params)
36
+ @packet_log_reader = @default_packet_log_reader
37
+ @log_directory = System.paths['LOGS']
38
+ @log_directory << '/' unless @log_directory[-1..-1] == '\\' or @log_directory[-1..-1] == '/'
39
+ @log_filename = nil
40
+ @playing = false
41
+ @playback_sleeper = nil
42
+ @thread = nil
43
+ @playback_index = 0
44
+ @playback_max_index = 0
45
+ @packet_offsets = []
46
+ @progress = 0
47
+ @status = ''
48
+ @start_time = ''
49
+ @current_time = ''
50
+ @end_time = ''
51
+ end
52
+
53
+ # Select and start analyzing a file for replay
54
+ #
55
+ # filename [String] filename relative to output logs folder or absolute filename
56
+ def select_file(filename, packet_log_reader = 'DEFAULT')
57
+ stop()
58
+ Cosmos.kill_thread(self, @thread)
59
+ @thread = Thread.new do
60
+ begin
61
+ stop()
62
+ if String === packet_log_reader
63
+ if packet_log_reader == 'DEFAULT'
64
+ @packet_log_reader = @default_packet_log_reader
65
+ else
66
+ packet_log_reader = Cosmos.require_class(packet_log_reader)
67
+ @packet_log_reader = packet_log_reader_class.new
68
+ end
69
+ elsif !packet_log_reader.nil?
70
+ # Instantiated object
71
+ @packet_log_reader = packet_log_reader
72
+ end # Else use existing
73
+
74
+ @log_filename = filename
75
+ @log_directory = File.dirname(@log_filename)
76
+ @log_directory << '/' unless @log_directory[-1..-1] == '\\'
77
+
78
+ System.telemetry.reset
79
+ @cancel = false
80
+ @progress = 0
81
+ @status = "Analyzing: #{@progress}%"
82
+ start_config_name = System.configuration_name
83
+ config_change_success, config_error = Cosmos.check_log_configuration(@packet_log_reader, @log_filename)
84
+ if System.configuration_name != start_config_name
85
+ @config_change_callback.call() if @config_change_callback
86
+ end
87
+ @packet_offsets = @packet_log_reader.packet_offsets(@log_filename, lambda {|percentage|
88
+ progress_int = (percentage * 100).to_i
89
+ if @progress != progress_int
90
+ @progress = progress_int
91
+ @status = "Analyzing: #{@progress}%"
92
+ end
93
+ @cancel
94
+ })
95
+ @playback_index = 0
96
+ @playback_max_index = @packet_offsets.length
97
+ @packet_log_reader.open(@log_filename)
98
+
99
+ if @cancel
100
+ @packet_log_reader.close
101
+ @log_filename = ''
102
+ @packet_offsets = []
103
+ @playback_index = 0
104
+ @start_time = ''
105
+ @current_time = ''
106
+ @end_time = ''
107
+ else
108
+ packet = read_at_index(@packet_offsets.length - 1, :FORWARD)
109
+ @end_time = packet.received_time.formatted(true, 3, true) if packet and packet.received_time
110
+ packet = read_at_index(0, :FORWARD)
111
+ @start_time = packet.received_time.formatted(true, 3, true) if packet and packet.received_time
112
+ end
113
+ rescue Exception => error
114
+ Logger.error "Error in Analysis Thread\n#{error.formatted}"
115
+ ensure
116
+ @status = 'Stopped'
117
+ @playing = false
118
+ @playback_sleeper = nil
119
+ @thread = nil
120
+ end
121
+ end
122
+ end
123
+
124
+ # Get current replay status
125
+ #
126
+ # @return [status, playback_delay, filename, file_start, file_current, file_end, file_index, file_max_index]
127
+ def status
128
+ [@status,
129
+ @playback_delay,
130
+ @log_filename.to_s,
131
+ @start_time,
132
+ @current_time,
133
+ @end_time,
134
+ @playback_index,
135
+ @playback_max_index]
136
+ end
137
+
138
+ # Set the replay delay
139
+ #
140
+ # @param delay [Float] delay between packets in seconds 0.0 to 1.0, nil = No Delay, -1.0 = REALTIME
141
+ def set_playback_delay(delay)
142
+ if delay
143
+ delay = delay.to_f
144
+ if delay <= 0.0
145
+ @playback_delay = 0.0
146
+ elsif delay > 1.0
147
+ @playback_delay = 1.0
148
+ else
149
+ @playback_delay = delay
150
+ end
151
+ else
152
+ @playback_delay = nil
153
+ end
154
+ end
155
+
156
+ # Replay start playing forward
157
+ def play
158
+ if @log_filename and !@thread
159
+ @playback_index = 1 if @playback_index < 0
160
+ start_playback(:FORWARD)
161
+ else
162
+ stop()
163
+ end
164
+ end
165
+
166
+ # Replay start playing backward
167
+ def reverse_play
168
+ if @log_filename and !@thread
169
+ @playback_index = @packet_offsets.length - 2 if @playback_index >= @packet_offsets.length
170
+ start_playback(:BACKWARD)
171
+ else
172
+ stop()
173
+ end
174
+ end
175
+
176
+ # Replay stop
177
+ def stop
178
+ @cancel = true
179
+ @playing = false
180
+ @playback_sleeper.cancel if @playback_sleeper
181
+ end
182
+
183
+ # Replay step forward one packet
184
+ def step_forward
185
+ if @log_filename and !@thread
186
+ @playback_index = 1 if @playback_index < 0
187
+ read_at_index(@playback_index, :FORWARD)
188
+ else
189
+ stop()
190
+ end
191
+ end
192
+
193
+ # Replay step backward one packet
194
+ def step_back
195
+ if @log_filename and !@thread
196
+ @playback_index = @packet_offsets.length - 2 if @playback_index >= @packet_offsets.length
197
+ read_at_index(@playback_index, :BACKWARD)
198
+ else
199
+ stop()
200
+ end
201
+ end
202
+
203
+ # Replay move to start of file
204
+ def move_start
205
+ if @log_filename and !@thread
206
+ packet = read_at_index(0, :FORWARD)
207
+ @start_time = packet.received_time.formatted(true, 3, true) if packet and packet.received_time
208
+ else
209
+ stop()
210
+ end
211
+ end
212
+
213
+ # Replay move to end of file
214
+ def move_end
215
+ if @log_filename and !@thread
216
+ packet = read_at_index(@packet_offsets.length - 1, :FORWARD)
217
+ @end_time = packet.received_time.formatted(true, 3, true) if packet and packet.received_time
218
+ else
219
+ stop()
220
+ end
221
+ end
222
+
223
+ # Replay move to index
224
+ #
225
+ # @param index [Integer] packet index into file
226
+ def move_index(index)
227
+ if @log_filename and !@thread
228
+ read_at_index(index, :FORWARD)
229
+ end
230
+ end
231
+
232
+ def shutdown
233
+ stop()
234
+ Cosmos.kill_thread(self, @thread)
235
+ reset()
236
+ end
237
+
238
+ # Gracefully kill threads
239
+ def graceful_kill
240
+ stop()
241
+ end
242
+
243
+ private
244
+
245
+ def start_playback(direction)
246
+ @thread = Thread.new do
247
+ @playback_sleeper = Sleeper.new
248
+ error = nil
249
+ begin
250
+ @playing = true
251
+ @status = 'Playing'
252
+
253
+ previous_packet = nil
254
+ while (@playing)
255
+ if @playback_delay != 0.0
256
+ packet_start = Time.now.sys
257
+ packet = read_at_index(@playback_index, direction)
258
+ break unless packet
259
+ delay_time = 0.0
260
+ if @playback_delay
261
+ # Fixed Time Delay
262
+ delay_time = @playback_delay - (Time.now.sys - packet_start)
263
+ elsif previous_packet and packet.received_time and previous_packet.received_time
264
+ # Realtime
265
+ if direction == :FORWARD
266
+ delay_time = packet.received_time - previous_packet.received_time - (Time.now.sys - packet_start)
267
+ else
268
+ delay_time = previous_packet.received_time - packet.received_time - (Time.now.sys - packet_start)
269
+ end
270
+ end
271
+ if delay_time > 0.0
272
+ break if @playback_sleeper.sleep(delay_time)
273
+ end
274
+ previous_packet = packet
275
+ else
276
+ # No Delay
277
+ packet = read_at_index(@playback_index, direction)
278
+ break unless packet
279
+ previous_packet = packet
280
+ end
281
+ end
282
+ rescue Exception => error
283
+ Logger.error "Error in Playback Thread\n#{error.formatted}"
284
+ ensure
285
+ @status = 'Stopped'
286
+ @playing = false
287
+ @playback_sleeper = nil
288
+ @thread = nil
289
+ end
290
+ end
291
+ end
292
+
293
+ def read_at_index(index, direction)
294
+ packet_offset = nil
295
+ packet_offset = @packet_offsets[index] if index >= 0
296
+ if packet_offset
297
+ # Read the packet
298
+ packet = @packet_log_reader.read_at_offset(packet_offset, false)
299
+ handle_packet(packet)
300
+
301
+ # Adjust index for next read
302
+ if direction == :FORWARD
303
+ @playback_index = index + 1
304
+ else
305
+ @playback_index = index - 1
306
+ end
307
+ @current_time = packet.received_time.formatted(true, 3, true) if packet and packet.received_time
308
+
309
+ return packet
310
+ else
311
+ return nil
312
+ end
313
+ end
314
+
315
+ def handle_packet(packet)
316
+ # For replay we will try our best here but not crash on errors
317
+ begin
318
+ interface = nil
319
+
320
+ # Identify and update packet
321
+ if packet.identified?
322
+ # Preidentifed packet - place it into the current value table
323
+ identified_packet = System.telemetry.update!(packet.target_name,
324
+ packet.packet_name,
325
+ packet.buffer)
326
+ else
327
+ # Packet needs to be identified
328
+ identified_packet = System.telemetry.identify!(packet.buffer)
329
+ end
330
+
331
+ if identified_packet and packet.target_name != 'UNKNOWN'
332
+ identified_packet.received_time = packet.received_time
333
+ packet = identified_packet
334
+ target = System.targets[packet.target_name.upcase]
335
+ interface = target.interface if target
336
+ else
337
+ unknown_packet = System.telemetry.update!('UNKNOWN', 'UNKNOWN', packet.buffer)
338
+ unknown_packet.received_time = packet.received_time
339
+ packet = unknown_packet
340
+ data_length = packet.length
341
+ string = "Unknown #{data_length} byte packet starting: "
342
+ num_bytes_to_print = [UNKNOWN_BYTES_TO_PRINT, data_length].min
343
+ data_to_print = packet.buffer(false)[0..(num_bytes_to_print - 1)]
344
+ data_to_print.each_byte do |byte|
345
+ string << sprintf("%02X", byte)
346
+ end
347
+ time_string = ''
348
+ time_string = packet.received_time.formatted << ' ' if packet.received_time
349
+ puts "#{time_string}ERROR: #{string}"
350
+ end
351
+
352
+ target = System.targets[packet.target_name]
353
+ target.tlm_cnt += 1 if target
354
+ packet.received_count += 1
355
+ packet.check_limits(System.limits_set)
356
+ CmdTlmServer.instance.post_packet(packet)
357
+
358
+ # Write to routers
359
+ if interface
360
+ interface.routers.each do |router|
361
+ begin
362
+ router.write(packet) if router.write_allowed? and router.connected?
363
+ rescue => err
364
+ Logger.error "Problem writing to router #{router.name} - #{err.class}:#{err.message}"
365
+ end
366
+ end
367
+ end
368
+ rescue Exception => err
369
+ Logger.error "Problem handling packet #{packet.target_name} #{packet.packet_name} - #{err.class}:#{err.message}"
370
+ end
371
+ end
372
+
373
+ end # class ReplayBackend
374
+
375
+ end # module Cosmos