cosmos 4.0.3 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -5
- data/Manifest.txt +11 -1
- data/README.md +3 -2
- data/Rakefile +18 -4
- data/appveyor.yml +19 -0
- data/cosmos.gemspec +14 -3
- data/data/config/cmd_tlm_server.yaml +3 -0
- data/data/crc.txt +63 -60
- data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
- data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
- data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
- data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
- data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
- data/demo/lib/cmd_sequence_exporter.rb +52 -0
- data/demo/lib/example_background_task.rb +1 -0
- data/demo/procedures/replay_test.rb +32 -0
- data/ext/cosmos/ext/structure/structure.c +39 -3
- data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
- data/install/config/tools/launcher/launcher.txt +2 -0
- data/lib/cosmos/config/config_parser.rb +2 -0
- data/lib/cosmos/core_ext/io.rb +89 -60
- data/lib/cosmos/gui/qt.rb +5 -8
- data/lib/cosmos/gui/qt_tool.rb +8 -8
- data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
- data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
- data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
- data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
- data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
- data/lib/cosmos/interfaces/udp_interface.rb +27 -14
- data/lib/cosmos/io/buffered_file.rb +0 -1
- data/lib/cosmos/io/json_drb.rb +134 -214
- data/lib/cosmos/io/json_drb_object.rb +22 -61
- data/lib/cosmos/io/json_drb_rack.rb +79 -0
- data/lib/cosmos/io/json_rpc.rb +27 -0
- data/lib/cosmos/io/udp_sockets.rb +102 -58
- data/lib/cosmos/packets/commands.rb +1 -1
- data/lib/cosmos/packets/structure.rb +1 -1
- data/lib/cosmos/packets/structure_item.rb +37 -5
- data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
- data/lib/cosmos/script/replay.rb +60 -0
- data/lib/cosmos/script/script.rb +20 -2
- data/lib/cosmos/script/scripting.rb +9 -9
- data/lib/cosmos/script/tools.rb +14 -0
- data/lib/cosmos/system/system.rb +185 -92
- data/lib/cosmos/system/target.rb +1 -1
- data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
- data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
- data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
- data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
- data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
- data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
- data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
- data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
- data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
- data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
- data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
- data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
- data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
- data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
- data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
- data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
- data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
- data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
- data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
- data/lib/cosmos/tools/replay/replay.rb +5 -505
- data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
- data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
- data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
- data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
- data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
- data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
- data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
- data/lib/cosmos/top_level.rb +34 -24
- data/lib/cosmos/utilities/csv.rb +60 -8
- data/lib/cosmos/version.rb +5 -5
- data/spec/config/config_parser_spec.rb +10 -1
- data/spec/core_ext/socket_spec.rb +4 -2
- data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
- data/spec/install/config/data/data.txt +1 -0
- data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
- data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
- data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
- data/spec/interfaces/serial_interface_spec.rb +1 -1
- data/spec/interfaces/udp_interface_spec.rb +14 -0
- data/spec/io/buffered_file_spec.rb +37 -0
- data/spec/io/json_drb_object_spec.rb +2 -15
- data/spec/io/json_drb_spec.rb +61 -121
- data/spec/io/udp_sockets_spec.rb +42 -2
- data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
- data/spec/packets/binary_accessor_spec.rb +1 -1
- data/spec/packets/packet_item_spec.rb +1 -1
- data/spec/packets/structure_item_spec.rb +5 -6
- data/spec/script/cmd_tlm_server_spec.rb +39 -4
- data/spec/script/commands_disconnect_spec.rb +1 -1
- data/spec/script/commands_spec.rb +2 -1
- data/spec/script/scripting_spec.rb +18 -3
- data/spec/script/telemetry_spec.rb +5 -0
- data/spec/spec_helper.rb +43 -26
- data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
- data/spec/system/system_spec.rb +11 -9
- data/spec/system/target_spec.rb +3 -0
- data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
- data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
- data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
- data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
- data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
- data/spec/tools/launcher/launcher_config_spec.rb +1 -1
- data/spec/tools/table_manager/table_item_spec.rb +1 -1
- data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
- data/spec/top_level/top_level_spec.rb +151 -3
- data/spec/utilities/csv_spec.rb +24 -5
- metadata +61 -9
- 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
|
-
@
|
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
|
32
|
-
populate(
|
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
|
39
|
-
populate(
|
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
|
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
|
54
|
-
state = @interfaces_table
|
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
|
77
|
-
@interfaces_table
|
78
|
-
@interfaces_table
|
79
|
-
@interfaces_table
|
80
|
-
@interfaces_table
|
81
|
-
if name == ROUTERS
|
82
|
-
@interfaces_table
|
83
|
-
@interfaces_table
|
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
|
86
|
-
@interfaces_table
|
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(
|
107
|
+
def populate(interfaces)
|
108
|
+
reset()
|
95
109
|
return if interfaces.all.empty?
|
96
110
|
|
97
|
-
|
98
|
-
|
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(
|
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
|
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(
|
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(
|
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(
|
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
|
-
@
|
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
|
28
|
-
|
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
|
-
@
|
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
|
28
|
-
|
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
|
32
|
-
populate(
|
41
|
+
def populate_commands
|
42
|
+
populate(System.commands)
|
33
43
|
end
|
34
44
|
|
35
|
-
def
|
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
|
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
|
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(
|
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
|
-
|
70
|
-
|
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(
|
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
|
108
|
+
@packets_table = table
|
98
109
|
end
|
99
110
|
|
100
|
-
def populate_packets_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
|