cosmos 4.0.3-java → 4.1.0-java

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 (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 +12 -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