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
@@ -20,38 +20,51 @@ module Cosmos
20
20
  ROUTERS = 'Routers'
21
21
  ALIGN_CENTER = Qt::AlignCenter
22
22
 
23
- def initialize(server_gui)
23
+ def initialize(server_gui, name, tab_widget)
24
24
  @server_gui = server_gui
25
- @interfaces_table = {}
25
+ @name = name
26
+ @widget = nil
27
+ reset()
28
+ @scroll = Qt::ScrollArea.new
29
+ @scroll.setMinimumSize(800, 150)
30
+ tab_widget.addTab(@scroll, @name)
31
+ end
32
+
33
+ def reset
34
+ Qt.execute_in_main_thread(true) do
35
+ @widget.destroy if @widget
36
+ @widget = nil
37
+ @interfaces_table = nil
38
+ end
26
39
  end
27
40
 
28
41
  # Create the interfaces tab and add it to the tab_widget
29
42
  #
30
43
  # @param tab_widget [Qt::TabWidget] The tab widget to add the tab to
31
- def populate_interfaces(tab_widget)
32
- populate(INTERFACES, CmdTlmServer.interfaces, tab_widget)
44
+ def populate_interfaces
45
+ populate(CmdTlmServer.interfaces)
33
46
  end
34
47
 
35
48
  # Create the routers tab and add it to the tab_widget
36
49
  #
37
50
  # @param tab_widget [Qt::TabWidget] The tab widget to add the tab to
38
- def populate_routers(tab_widget)
39
- populate(ROUTERS, CmdTlmServer.routers, tab_widget)
51
+ def populate_routers
52
+ populate(CmdTlmServer.routers)
40
53
  end
41
54
 
42
55
  # Update the interfaces or routers tab
43
56
  #
44
57
  # @param name [String] Must be Interfaces or Routers
45
- def update(name)
46
- if name == ROUTERS
58
+ def update
59
+ if @name == ROUTERS
47
60
  interfaces = CmdTlmServer.routers
48
61
  else
49
62
  interfaces = CmdTlmServer.interfaces
50
63
  end
51
64
  row = 0
52
65
  interfaces.all.each do |interface_name, interface|
53
- button = @interfaces_table[name].cellWidget(row,1)
54
- state = @interfaces_table[name].item(row,2)
66
+ button = @interfaces_table.cellWidget(row,1)
67
+ state = @interfaces_table.item(row,2)
55
68
  if interface.connected? and interface.thread
56
69
  button.setText('Disconnect')
57
70
  button.setDisabled(true) if interface.disable_disconnect
@@ -73,17 +86,17 @@ module Cosmos
73
86
  state.setText('false')
74
87
  state.textColor = Cosmos::BLACK
75
88
  end
76
- @interfaces_table[name].item(row,3).setText(interface.num_clients.to_s)
77
- @interfaces_table[name].item(row,4).setText(interface.write_queue_size.to_s)
78
- @interfaces_table[name].item(row,5).setText(interface.read_queue_size.to_s)
79
- @interfaces_table[name].item(row,6).setText(interface.bytes_written.to_s)
80
- @interfaces_table[name].item(row,7).setText(interface.bytes_read.to_s)
81
- if name == ROUTERS
82
- @interfaces_table[name].item(row,8).setText(interface.read_count.to_s)
83
- @interfaces_table[name].item(row,9).setText(interface.write_count.to_s)
89
+ @interfaces_table.item(row,3).setText(interface.num_clients.to_s)
90
+ @interfaces_table.item(row,4).setText(interface.write_queue_size.to_s)
91
+ @interfaces_table.item(row,5).setText(interface.read_queue_size.to_s)
92
+ @interfaces_table.item(row,6).setText(interface.bytes_written.to_s)
93
+ @interfaces_table.item(row,7).setText(interface.bytes_read.to_s)
94
+ if @name == ROUTERS
95
+ @interfaces_table.item(row,8).setText(interface.read_count.to_s)
96
+ @interfaces_table.item(row,9).setText(interface.write_count.to_s)
84
97
  else
85
- @interfaces_table[name].item(row,8).setText(interface.write_count.to_s)
86
- @interfaces_table[name].item(row,9).setText(interface.read_count.to_s)
98
+ @interfaces_table.item(row,8).setText(interface.write_count.to_s)
99
+ @interfaces_table.item(row,9).setText(interface.read_count.to_s)
87
100
  end
88
101
  row += 1
89
102
  end
@@ -91,13 +104,12 @@ module Cosmos
91
104
 
92
105
  private
93
106
 
94
- def populate(name, interfaces, tab_widget)
107
+ def populate(interfaces)
108
+ reset()
95
109
  return if interfaces.all.empty?
96
110
 
97
- scroll = Qt::ScrollArea.new
98
- scroll.setMinimumSize(800, 150)
99
- widget = Qt::Widget.new
100
- layout = Qt::VBoxLayout.new(widget)
111
+ @widget = Qt::Widget.new
112
+ layout = Qt::VBoxLayout.new(@widget)
101
113
  # Since the layout will be inside a scroll area make sure it respects the sizes we set
102
114
  layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
103
115
 
@@ -105,28 +117,27 @@ module Cosmos
105
117
  interfaces_table.verticalHeader.hide()
106
118
  interfaces_table.setRowCount(interfaces.all.length)
107
119
  interfaces_table.setColumnCount(11)
108
- if name == ROUTERS
120
+ if @name == ROUTERS
109
121
  interfaces_table.setHorizontalHeaderLabels(["Router", "Connect/Disconnect", "Connected?", "Clients", "Tx Q Size", "Rx Q Size", " Bytes Tx ", " Bytes Rx ", " Pkts Rcvd ", " Pkts Sent ", "View Raw"])
110
122
  else
111
123
  interfaces_table.setHorizontalHeaderLabels(["Interface", "Connect/Disconnect", "Connected?", "Clients", "Tx Q Size", "Rx Q Size", " Bytes Tx ", " Bytes Rx ", " Cmd Pkts ", " Tlm Pkts ", "View Raw"])
112
124
  end
113
125
 
114
- populate_interface_table(name, interfaces, interfaces_table)
126
+ populate_interface_table(interfaces, interfaces_table)
115
127
  interfaces_table.displayFullSize
116
128
 
117
129
  layout.addWidget(interfaces_table)
118
- scroll.setWidget(widget)
119
- @interfaces_table[name] = interfaces_table
120
- tab_widget.addTab(scroll, name)
130
+ @scroll.setWidget(@widget)
131
+ @interfaces_table = interfaces_table
121
132
  end
122
133
 
123
- def populate_interface_table(name, interfaces, interfaces_table)
134
+ def populate_interface_table(interfaces, interfaces_table)
124
135
  row = 0
125
136
  interfaces.all.each do |interface_name, interface|
126
137
  item = Qt::TableWidgetItem.new(Qt::Object.tr(interface_name))
127
138
  item.setTextAlignment(ALIGN_CENTER)
128
139
  interfaces_table.setItem(row, 0, item)
129
- interfaces_table.setCellWidget(row, 1, create_button(name, interface, interface_name))
140
+ interfaces_table.setCellWidget(row, 1, create_button(interface, interface_name))
130
141
  interfaces_table.setItem(row, 2, create_state(interface))
131
142
 
132
143
  index = 3
@@ -142,7 +153,7 @@ module Cosmos
142
153
  view_raw = Qt::PushButton.new("View Raw")
143
154
  view_raw.connect(SIGNAL('clicked()')) do
144
155
  @raw_dialogs ||= []
145
- if name == ROUTERS
156
+ if @name == ROUTERS
146
157
  current_interface = CmdTlmServer.routers.all[interface_name]
147
158
  else
148
159
  current_interface = CmdTlmServer.interfaces.all[interface_name]
@@ -154,7 +165,7 @@ module Cosmos
154
165
  end
155
166
  end
156
167
 
157
- def create_button(name, interface, interface_name)
168
+ def create_button(interface, interface_name)
158
169
  if interface.connected? and interface.thread
159
170
  button_text = 'Disconnect'
160
171
  elsif interface.thread
@@ -165,7 +176,7 @@ module Cosmos
165
176
  button_text = 'Connect'
166
177
  end
167
178
  button = Qt::PushButton.new(button_text)
168
- if name == ROUTERS
179
+ if @name == ROUTERS
169
180
  button.connect(SIGNAL('clicked()')) do
170
181
  if CmdTlmServer.routers.all[interface_name].thread
171
182
  Logger.info "User disconnecting router #{interface_name}"
@@ -16,18 +16,29 @@ module Cosmos
16
16
  # Implements the logging tab in the Command and Telemetry Server GUI
17
17
  class LoggingTab
18
18
 
19
- def initialize(production)
19
+ def initialize(production, tab_widget)
20
20
  @production = production
21
- @logging_layouts = {}
21
+ @widget = nil
22
+ reset()
23
+ @scroll = Qt::ScrollArea.new
24
+ tab_widget.addTab(@scroll, "Logging")
25
+ end
26
+
27
+ def reset
28
+ Qt.execute_in_main_thread(true) do
29
+ @widget.destroy if @widget
30
+ @widget = nil
31
+ @logging_layouts = {}
32
+ end
22
33
  end
23
34
 
24
35
  # Create the logging tab and add it to the tab_widget
25
36
  #
26
37
  # @param tab_widget [Qt::TabWidget] The tab widget to add the tab to
27
- def populate(tab_widget)
28
- scroll = Qt::ScrollArea.new
29
- widget = Qt::Widget.new
30
- layout = Qt::VBoxLayout.new(widget)
38
+ def populate
39
+ reset()
40
+ @widget = Qt::Widget.new
41
+ layout = Qt::VBoxLayout.new(@widget)
31
42
  # Since the layout will be inside a scroll area
32
43
  # make sure it respects the sizes we set
33
44
  layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
@@ -38,8 +49,7 @@ module Cosmos
38
49
  populate_log_file_info(layout)
39
50
 
40
51
  # Set the scroll area widget last now that all the items have been layed out
41
- scroll.setWidget(widget)
42
- tab_widget.addTab(scroll, "Logging")
52
+ @scroll.setWidget(@widget)
43
53
  end
44
54
 
45
55
  def update
@@ -19,23 +19,37 @@ module Cosmos
19
19
  COMMANDS = "Commands"
20
20
  TELEMETRY = "Telemetry"
21
21
 
22
- def initialize(server_gui)
22
+ def initialize(server_gui, name, tab_widget)
23
23
  @server_gui = server_gui
24
- @packets_table = {}
24
+ @name = name
25
+ @widget = nil
26
+ reset()
27
+ @scroll = Qt::ScrollArea.new
28
+ tab_name = "Cmd Packets" if name == COMMANDS
29
+ tab_name = "Tlm Packets" if name == TELEMETRY
30
+ tab_widget.addTab(@scroll, tab_name)
25
31
  end
26
32
 
27
- def populate_commands(tab_widget)
28
- populate(COMMANDS, System.commands, tab_widget)
33
+ def reset
34
+ Qt.execute_in_main_thread(true) do
35
+ @widget.destroy if @widget
36
+ @widget = nil
37
+ @packets_table = nil
38
+ end
29
39
  end
30
40
 
31
- def populate_telemetry(tab_widget)
32
- populate(TELEMETRY, System.telemetry, tab_widget)
41
+ def populate_commands
42
+ populate(System.commands)
33
43
  end
34
44
 
35
- def update(name)
45
+ def populate_telemetry
46
+ populate(System.telemetry)
47
+ end
48
+
49
+ def update
36
50
  cmd_tlm = nil
37
- cmd_tlm = System.commands if name == COMMANDS
38
- cmd_tlm = System.telemetry if name == TELEMETRY
51
+ cmd_tlm = System.commands if @name == COMMANDS
52
+ cmd_tlm = System.telemetry if @name == TELEMETRY
39
53
  return if cmd_tlm.nil? || cmd_tlm.target_names.empty?
40
54
 
41
55
  row = 0
@@ -43,18 +57,19 @@ module Cosmos
43
57
  packets = cmd_tlm.packets(target_name)
44
58
  packets.sort.each do |packet_name, packet|
45
59
  next if packet.hidden
46
- @packets_table[name].item(row, 2).setText(packet.received_count.to_s)
60
+ @packets_table.item(row, 2).setText(packet.received_count.to_s)
47
61
  row += 1
48
62
  end
49
63
  end
50
64
  packet = cmd_tlm.packet('UNKNOWN', 'UNKNOWN')
51
- @packets_table[name].item(row, 2).setText(packet.received_count.to_s)
65
+ @packets_table.item(row, 2).setText(packet.received_count.to_s)
52
66
  row += 1
53
67
  end
54
68
 
55
69
  private
56
70
 
57
- def populate(name, cmd_tlm, tab_widget)
71
+ def populate(cmd_tlm)
72
+ reset()
58
73
  return if cmd_tlm.target_names.empty?
59
74
 
60
75
  count = 0
@@ -66,9 +81,8 @@ module Cosmos
66
81
  end
67
82
  count += 1 # For UNKNOWN UNKNOWN
68
83
 
69
- scroll = Qt::ScrollArea.new
70
- widget = Qt::Widget.new
71
- layout = Qt::VBoxLayout.new(widget)
84
+ @widget = Qt::Widget.new
85
+ layout = Qt::VBoxLayout.new(@widget)
72
86
  # Since the layout will be inside a scroll area
73
87
  # make sure it respects the sizes we set
74
88
  layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
@@ -81,23 +95,20 @@ module Cosmos
81
95
  # Force the last section to fill all available space in the frame
82
96
  #~ table.horizontalHeader.setStretchLastSection(true)
83
97
  headers = ["Target Name", "Packet Name", "Packet Count", "View Raw"]
84
- headers << "View in Command Sender" if name == COMMANDS
85
- headers << "View in Packet Viewer" if name == TELEMETRY
98
+ headers << "View in Command Sender" if @name == COMMANDS
99
+ headers << "View in Packet Viewer" if @name == TELEMETRY
86
100
  table.setHorizontalHeaderLabels(headers)
87
101
 
88
- populate_packets_table(name, cmd_tlm, table)
102
+ populate_packets_table(cmd_tlm, table)
89
103
  table.displayFullSize
90
104
 
91
105
  layout.addWidget(table)
92
- scroll.setWidget(widget)
93
- tab_name = "Cmd Packets" if name == COMMANDS
94
- tab_name = "Tlm Packets" if name == TELEMETRY
95
- tab_widget.addTab(scroll, tab_name)
106
+ @scroll.setWidget(@widget)
96
107
 
97
- @packets_table[name] = table
108
+ @packets_table = table
98
109
  end
99
110
 
100
- def populate_packets_table(name, cmd_tlm, table)
111
+ def populate_packets_table(cmd_tlm, table)
101
112
  row = 0
102
113
  target_names = cmd_tlm.target_names
103
114
  target_names << 'UNKNOWN'.freeze
@@ -116,14 +127,14 @@ module Cosmos
116
127
  view_raw = Qt::PushButton.new("View Raw")
117
128
  view_raw.connect(SIGNAL('clicked()')) do
118
129
  @raw_dialogs ||= []
119
- @raw_dialogs << CmdRawDialog.new(@server_gui, target_name, packet_name) if name == COMMANDS
120
- @raw_dialogs << TlmRawDialog.new(@server_gui, target_name, packet_name) if name == TELEMETRY
130
+ @raw_dialogs << CmdRawDialog.new(@server_gui, target_name, packet_name) if @name == COMMANDS
131
+ @raw_dialogs << TlmRawDialog.new(@server_gui, target_name, packet_name) if @name == TELEMETRY
121
132
  end
122
133
  table.setCellWidget(row, 3, view_raw)
123
134
 
124
- if name == COMMANDS
135
+ if @name == COMMANDS
125
136
  add_tool_button(table, row, target_name, packet_name, "Command Sender")
126
- elsif name == TELEMETRY
137
+ elsif @name == TELEMETRY
127
138
  add_tool_button(table, row, target_name, packet_name, "Packet Viewer")
128
139
  end
129
140
 
@@ -0,0 +1,242 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2014 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
+ require 'cosmos'
12
+ require 'cosmos/gui/qt'
13
+ require 'cosmos/gui/choosers/string_chooser'
14
+ require 'cosmos/gui/dialogs/packet_log_dialog'
15
+ require 'cosmos/gui/dialogs/progress_dialog'
16
+
17
+ module Cosmos
18
+
19
+ # Implements the replay tab in the Command and Telemetry Server GUI
20
+ class ReplayTab
21
+
22
+ attr_accessor :widget
23
+
24
+ def initialize(tab_widget)
25
+ @widget = nil
26
+ @update_slider = true
27
+ @file_max_index = 0
28
+ reset()
29
+ @scroll = Qt::ScrollArea.new
30
+ tab_widget.addTab(@scroll, "Replay")
31
+ end
32
+
33
+ def reset
34
+ CmdTlmServer.replay_backend.reset if CmdTlmServer.instance
35
+ Qt.execute_in_main_thread(true) do
36
+ @widget.destroy if @widget
37
+ @widget = nil
38
+ end
39
+ end
40
+
41
+ # Create the targets tab and add it to the tab_widget
42
+ #
43
+ # @param tab_widget [Qt::TabWidget] The tab widget to add the tab to
44
+ def populate
45
+ return if @widget
46
+
47
+ @widget = Qt::Widget.new
48
+
49
+ layout = Qt::VBoxLayout.new(@widget)
50
+ # Since the layout will be inside a scroll area make sure it respects the sizes we set
51
+ layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
52
+
53
+ @log_widget = Qt::Widget.new
54
+ @log_widget.setSizePolicy(Qt::SizePolicy::MinimumExpanding, Qt::SizePolicy::MinimumExpanding)
55
+ @log_layout = Qt::VBoxLayout.new()
56
+
57
+ # This widget goes inside the top layout so we want 0 contents margins
58
+ @log_layout.setContentsMargins(0,0,0,0)
59
+ @log_widget.setLayout(@log_layout)
60
+
61
+ # Create the log file GUI
62
+ @log_file_selection = Qt::GroupBox.new("Log File Selection")
63
+ @log_select = Qt::HBoxLayout.new(@log_file_selection)
64
+ @log_name = Qt::LineEdit.new
65
+ @log_name.setReadOnly(true)
66
+ @log_select.addWidget(@log_name)
67
+ @log_open = Qt::PushButton.new("Browse...")
68
+ @log_select.addWidget(@log_open)
69
+ @log_layout.addWidget(@log_file_selection)
70
+
71
+ @log_open.connect(SIGNAL('clicked()')) { select_log_file() }
72
+
73
+ # Create the operation buttons GUI
74
+ @op = Qt::GroupBox.new("Playback Control")
75
+ @op_layout = Qt::VBoxLayout.new(@op)
76
+ @op_button_layout = Qt::HBoxLayout.new
77
+ @move_start = Qt::PushButton.new(Cosmos.get_icon('skip_to_start-26.png'), '')
78
+ @move_start.connect(SIGNAL('clicked()')) { CmdTlmServer.replay_backend.move_start() }
79
+ @op_button_layout.addWidget(@move_start)
80
+ @step_back = Qt::PushButton.new(Cosmos.get_icon('rewind-26.png'), '')
81
+ @step_back_timer = Qt::Timer.new
82
+ @step_back_timeout = 100
83
+ @step_back_timer.connect(SIGNAL('timeout()')) { CmdTlmServer.replay_backend.step_back(); @step_back_timeout = (@step_back_timeout / 2).to_i; @step_back_timer.start(@step_back_timeout) }
84
+ @step_back.connect(SIGNAL('pressed()')) { CmdTlmServer.replay_backend.step_back(); @step_back_timeout = 300; @step_back_timer.start(@step_back_timeout) }
85
+ @step_back.connect(SIGNAL('released()')) { @step_back_timer.stop }
86
+ @op_button_layout.addWidget(@step_back)
87
+ @reverse_play = Qt::PushButton.new(Cosmos.get_icon('reverse-play-26.png'), '')
88
+ @reverse_play.connect(SIGNAL('clicked()')) { CmdTlmServer.replay_backend.reverse_play() }
89
+ @op_button_layout.addWidget(@reverse_play)
90
+ @stop = Qt::PushButton.new(Cosmos.get_icon('stop-26.png'), '')
91
+ @stop.connect(SIGNAL('clicked()')) { CmdTlmServer.replay_backend.stop() }
92
+ @op_button_layout.addWidget(@stop)
93
+ @play = Qt::PushButton.new(Cosmos.get_icon('play-26.png'), '')
94
+ @play.connect(SIGNAL('clicked()')) { CmdTlmServer.replay_backend.play() }
95
+ @op_button_layout.addWidget(@play)
96
+ @step_forward = Qt::PushButton.new(Cosmos.get_icon('fast_forward-26.png'), '')
97
+ @step_forward_timer = Qt::Timer.new
98
+ @step_forward_timeout = 100
99
+ @step_forward_timer.connect(SIGNAL('timeout()')) { CmdTlmServer.replay_backend.step_forward(); @step_forward_timeout = (@step_forward_timeout / 2).to_i; @step_forward_timer.start(@step_forward_timeout) }
100
+ @step_forward.connect(SIGNAL('pressed()')) { CmdTlmServer.replay_backend.step_forward(); @step_forward_timeout = 300; @step_forward_timer.start(@step_forward_timeout) }
101
+ @step_forward.connect(SIGNAL('released()')) { @step_forward_timer.stop }
102
+ @op_button_layout.addWidget(@step_forward)
103
+ @move_end = Qt::PushButton.new(Cosmos.get_icon('end-26.png'), '')
104
+ @move_end.connect(SIGNAL('clicked()')) { CmdTlmServer.replay_backend.move_end() }
105
+ @op_button_layout.addWidget(@move_end)
106
+ @op_layout.addLayout(@op_button_layout)
107
+
108
+ # Speed Selection
109
+ @playback_delay = nil
110
+ @speed_select = Qt::ComboBox.new
111
+ @variants = []
112
+ @variants << [Qt::Variant.new(0.0), 0.0]
113
+ @speed_select.addItem("No Delay", @variants[-1][0])
114
+ @variants << [Qt::Variant.new(0.001), 0.001]
115
+ @speed_select.addItem("1ms Delay", @variants[-1][0])
116
+ @variants << [Qt::Variant.new(0.002), 0.002]
117
+ @speed_select.addItem("2ms Delay", @variants[-1][0])
118
+ @variants << [Qt::Variant.new(0.005), 0.005]
119
+ @speed_select.addItem("5ms Delay", @variants[-1][0])
120
+ @variants << [Qt::Variant.new(0.01), 0.01]
121
+ @speed_select.addItem("10ms Delay", @variants[-1][0])
122
+ @variants << [Qt::Variant.new(0.05), 0.05]
123
+ @speed_select.addItem("50ms Delay", @variants[-1][0])
124
+ @variants << [Qt::Variant.new(0.125), 0.125]
125
+ @speed_select.addItem("125ms Delay", @variants[-1][0])
126
+ @variants << [Qt::Variant.new(0.25), 0.25]
127
+ @speed_select.addItem("250ms Delay", @variants[-1][0])
128
+ @variants << [Qt::Variant.new(0.5), 0.5]
129
+ @speed_select.addItem("500ms Delay", @variants[-1][0])
130
+ @variants << [Qt::Variant.new(1.0), 1.0]
131
+ @speed_select.addItem("1s Delay", @variants[-1][0])
132
+ @variants << [Qt::Variant.new(nil), nil]
133
+ @speed_select.addItem("Realtime", @variants[-1][0])
134
+ @speed_select.setMaxVisibleItems(11)
135
+ @speed_select.connect(SIGNAL('currentIndexChanged(int)')) do
136
+ CmdTlmServer.replay_backend.set_playback_delay(@speed_select.itemData(@speed_select.currentIndex).value)
137
+ end
138
+ @speed_layout = Qt::FormLayout.new()
139
+ @speed_layout.addRow("&Delay:", @speed_select)
140
+ @status = Qt::LineEdit.new
141
+ @status.setReadOnly(true)
142
+ @status.setText('Stopped')
143
+ @speed_layout.addRow("&Status:", @status)
144
+ @op_layout.addLayout(@speed_layout)
145
+ @log_layout.addWidget(@op)
146
+
147
+ @file_pos = Qt::GroupBox.new("File Position")
148
+ @file_pos_layout = Qt::VBoxLayout.new(@file_pos)
149
+ @slider = Qt::Slider.new(Qt::Horizontal)
150
+ @slider.setRange(0, 10000)
151
+ @slider.setTickInterval(1000)
152
+ @slider.setTickPosition(Qt::Slider::TicksBothSides)
153
+ @slider.setTracking(false)
154
+ @slider.connect(SIGNAL('sliderPressed()')) { slider_pressed() }
155
+ @slider.connect(SIGNAL('sliderReleased()')) { slider_released() }
156
+ @time_layout = Qt::HBoxLayout.new()
157
+ @start_time = StringChooser.new(@widget, 'Start:', '', 200, true, true, Qt::AlignCenter | Qt::AlignVCenter)
158
+ @end_time = StringChooser.new(@widget, 'End:', '', 200, true, true, Qt::AlignCenter | Qt::AlignVCenter)
159
+ @current_time = StringChooser.new(@widget, 'Current:', '', 200, true, true, Qt::AlignCenter | Qt::AlignVCenter)
160
+ @time_layout.addWidget(@start_time)
161
+ @time_layout.addWidget(@current_time)
162
+ @time_layout.addWidget(@end_time)
163
+ @file_pos_layout.addLayout(@time_layout)
164
+ @file_pos_layout.addWidget(@slider)
165
+ @log_layout.addWidget(@file_pos)
166
+ layout.addWidget(@log_widget)
167
+
168
+ @scroll.setWidget(@widget)
169
+ end
170
+
171
+ # Update the replay tab gui
172
+ def update
173
+ status, playback_delay, filename, file_start, file_current, file_end, file_index, @file_max_index = CmdTlmServer.replay_backend.status
174
+ @status.setText(status)
175
+ @log_name.text = filename
176
+ found = false
177
+ @variants.each_with_index do |data, index|
178
+ value = data[1]
179
+ if !playback_delay.nil? and !value.nil?
180
+ if (playback_delay >= (value - 0.0001)) and (playback_delay <= (value + 0.0001))
181
+ @speed_select.currentIndex = index
182
+ found = true
183
+ break
184
+ end
185
+ else
186
+ if playback_delay == value
187
+ @speed_select.currentIndex = index
188
+ found = true
189
+ break
190
+ end
191
+ end
192
+ end
193
+ unless found
194
+ @variants << [Qt::Variant.new(playback_delay.to_f), playback_delay.to_f]
195
+ @speed_select.addItem("#{(playback_delay.to_f * 1000.0).to_i}ms Delay", @variants[-1][0])
196
+ @speed_select.currentIndex = @variants.length - 1
197
+ end
198
+ @start_time.value = file_start
199
+ @current_time.value = file_current
200
+ @end_time.value = file_end
201
+
202
+ if @update_slider
203
+ if @file_max_index != 0
204
+ value = (((file_index - 1).to_f / @file_max_index.to_f) * 10000.0).to_i
205
+ else
206
+ value = 0
207
+ end
208
+ @slider.setSliderPosition(value)
209
+ @slider.setValue(value)
210
+ end
211
+ end
212
+
213
+ def slider_pressed
214
+ @update_slider = false
215
+ CmdTlmServer.replay_backend.stop
216
+ end
217
+
218
+ def slider_released
219
+ CmdTlmServer.replay_backend.move_index(((@slider.sliderPosition / 10000.0) * (@file_max_index - 1)).to_i)
220
+ @update_slider = true
221
+ end
222
+
223
+ def shutdown
224
+ CmdTlmServer.replay_backend.shutdown
225
+ end
226
+
227
+ private
228
+
229
+ def select_log_file
230
+ packet_log_dialog = PacketLogDialog.new(
231
+ @widget, 'Select Log File', CmdTlmServer.replay_backend.log_directory, CmdTlmServer.replay_backend.packet_log_reader,
232
+ [], nil, false, false, true, Cosmos::TLM_FILE_PATTERN,
233
+ Cosmos::BIN_FILE_PATTERN, false
234
+ )
235
+ case packet_log_dialog.exec
236
+ when Qt::Dialog::Accepted
237
+ CmdTlmServer.replay_backend.select_file(packet_log_dialog.filenames[0], packet_log_dialog.packet_log_reader)
238
+ end
239
+ end
240
+
241
+ end
242
+ end # module Cosmos