cosmos 3.4.2 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (261) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +69 -11
  3. data/autohotkey/config/targets/INST/cmd_tlm/inst_tlm.txt +23 -0
  4. data/autohotkey/tools/TestRunnerAHK5 +17 -0
  5. data/autohotkey/tools/TestRunnerAHK6 +17 -0
  6. data/autohotkey/tools/cmd_extractor.ahk +6 -0
  7. data/autohotkey/tools/data_viewer.ahk +6 -0
  8. data/autohotkey/tools/limits_monitor.ahk +67 -14
  9. data/autohotkey/tools/replay.ahk +6 -0
  10. data/autohotkey/tools/test_runner5.ahk +8 -0
  11. data/autohotkey/tools/test_runner6.ahk +5 -0
  12. data/autohotkey/tools/tlm_extractor.ahk +25 -1
  13. data/autohotkey/tools/tlm_grapher.ahk +6 -0
  14. data/cosmos.gemspec +19 -19
  15. data/data/crc.txt +46 -46
  16. data/data/critical.wav +0 -0
  17. data/data/information.wav +0 -0
  18. data/data/input.wav +0 -0
  19. data/data/message.wav +0 -0
  20. data/data/question.wav +0 -0
  21. data/data/warning.wav +0 -0
  22. data/demo/Gemfile +5 -1
  23. data/demo/Launcher +5 -4
  24. data/demo/Launcher.bat +6 -56
  25. data/demo/config/data/crc.txt +73 -55
  26. data/demo/config/system/system.txt +1 -0
  27. data/demo/config/targets/EXAMPLE/cmd_tlm/example_tlm.txt +2 -0
  28. data/demo/procedures/example_test.rb +17 -16
  29. data/demo/tools/CmdExtractor +6 -5
  30. data/demo/tools/CmdExtractor.bat +6 -56
  31. data/demo/tools/CmdSender +6 -5
  32. data/demo/tools/CmdSender.bat +6 -56
  33. data/demo/tools/CmdTlmServer +6 -5
  34. data/demo/tools/CmdTlmServer.bat +6 -56
  35. data/demo/tools/DataViewer +6 -5
  36. data/demo/tools/DataViewer.bat +6 -56
  37. data/demo/tools/ExampleTarget +6 -5
  38. data/demo/tools/ExampleTarget.bat +6 -56
  39. data/demo/tools/HandbookCreator +6 -5
  40. data/demo/tools/HandbookCreator.bat +6 -58
  41. data/demo/tools/Launcher +6 -5
  42. data/demo/tools/Launcher.bat +6 -56
  43. data/demo/tools/LimitsMonitor +6 -5
  44. data/demo/tools/LimitsMonitor.bat +6 -56
  45. data/demo/tools/OpenGLBuilder +6 -5
  46. data/demo/tools/OpenGLBuilder.bat +6 -56
  47. data/demo/tools/PacketViewer +6 -5
  48. data/demo/tools/PacketViewer.bat +6 -56
  49. data/demo/tools/Replay +6 -5
  50. data/demo/tools/Replay.bat +6 -56
  51. data/demo/tools/ScpiTarget +6 -5
  52. data/demo/tools/ScpiTarget.bat +6 -56
  53. data/demo/tools/ScriptRunner +6 -5
  54. data/demo/tools/ScriptRunner.bat +6 -56
  55. data/demo/tools/TableManager +6 -5
  56. data/demo/tools/TableManager.bat +6 -56
  57. data/demo/tools/TestRunner +6 -5
  58. data/demo/tools/TestRunner.bat +6 -56
  59. data/demo/tools/TlmExtractor +6 -5
  60. data/demo/tools/TlmExtractor.bat +6 -56
  61. data/demo/tools/TlmGrapher +6 -5
  62. data/demo/tools/TlmGrapher.bat +6 -56
  63. data/demo/tools/TlmViewer +6 -5
  64. data/demo/tools/TlmViewer.bat +6 -56
  65. data/demo/tools/ToolLaunch.bat +63 -0
  66. data/demo/tools/mac/CmdExtractor.app/Contents/MacOS/CmdExtractor.rb +6 -5
  67. data/demo/tools/mac/CmdExtractor.app/Contents/MacOS/tool_launch.rb +38 -0
  68. data/demo/tools/mac/CmdSender.app/Contents/MacOS/CmdSender.rb +6 -5
  69. data/demo/tools/mac/CmdSender.app/Contents/MacOS/tool_launch.rb +38 -0
  70. data/demo/tools/mac/CmdTlmServer.app/Contents/MacOS/CmdTlmServer.rb +6 -5
  71. data/demo/tools/mac/CmdTlmServer.app/Contents/MacOS/tool_launch.rb +38 -0
  72. data/demo/tools/mac/DataViewer.app/Contents/MacOS/DataViewer.rb +6 -5
  73. data/demo/tools/mac/DataViewer.app/Contents/MacOS/tool_launch.rb +38 -0
  74. data/demo/tools/mac/HandbookCreator.app/Contents/MacOS/HandbookCreator.rb +6 -5
  75. data/demo/tools/mac/HandbookCreator.app/Contents/MacOS/tool_launch.rb +38 -0
  76. data/demo/tools/mac/Launcher.app/Contents/MacOS/Launcher.rb +6 -5
  77. data/demo/tools/mac/Launcher.app/Contents/MacOS/tool_launch.rb +38 -0
  78. data/demo/tools/mac/LimitsMonitor.app/Contents/MacOS/LimitsMonitor.rb +6 -5
  79. data/demo/tools/mac/LimitsMonitor.app/Contents/MacOS/tool_launch.rb +38 -0
  80. data/demo/tools/mac/OpenGLBuilder.app/Contents/MacOS/OpenGLBuilder.rb +6 -5
  81. data/demo/tools/mac/OpenGLBuilder.app/Contents/MacOS/tool_launch.rb +38 -0
  82. data/demo/tools/mac/PacketViewer.app/Contents/MacOS/PacketViewer.rb +6 -5
  83. data/demo/tools/mac/PacketViewer.app/Contents/MacOS/tool_launch.rb +38 -0
  84. data/demo/tools/mac/Replay.app/Contents/MacOS/Replay.rb +6 -5
  85. data/demo/tools/mac/Replay.app/Contents/MacOS/tool_launch.rb +38 -0
  86. data/demo/tools/mac/ScriptRunner.app/Contents/MacOS/ScriptRunner.rb +6 -5
  87. data/demo/tools/mac/ScriptRunner.app/Contents/MacOS/tool_launch.rb +38 -0
  88. data/demo/tools/mac/TableManager.app/Contents/MacOS/TableManager.rb +6 -5
  89. data/demo/tools/mac/TableManager.app/Contents/MacOS/tool_launch.rb +38 -0
  90. data/demo/tools/mac/TestRunner.app/Contents/MacOS/TestRunner.rb +6 -5
  91. data/demo/tools/mac/TestRunner.app/Contents/MacOS/tool_launch.rb +38 -0
  92. data/demo/tools/mac/TlmExtractor.app/Contents/MacOS/TlmExtractor.rb +6 -5
  93. data/demo/tools/mac/TlmExtractor.app/Contents/MacOS/tool_launch.rb +38 -0
  94. data/demo/tools/mac/TlmGrapher.app/Contents/MacOS/TlmGrapher.rb +6 -5
  95. data/demo/tools/mac/TlmGrapher.app/Contents/MacOS/tool_launch.rb +38 -0
  96. data/demo/tools/mac/TlmViewer.app/Contents/MacOS/TlmViewer.rb +6 -5
  97. data/demo/tools/mac/TlmViewer.app/Contents/MacOS/tool_launch.rb +38 -0
  98. data/demo/tools/tool_launch.rb +38 -0
  99. data/install/Gemfile +5 -1
  100. data/install/Launcher +5 -3
  101. data/install/Launcher.bat +6 -56
  102. data/install/config/data/crc.txt +67 -49
  103. data/install/config/tools/launcher/launcher.txt +1 -0
  104. data/install/tools/CmdExtractor +6 -5
  105. data/install/tools/CmdExtractor.bat +6 -56
  106. data/install/tools/CmdSender +6 -5
  107. data/install/tools/CmdSender.bat +6 -56
  108. data/install/tools/CmdTlmServer +6 -5
  109. data/install/tools/CmdTlmServer.bat +6 -56
  110. data/install/tools/DataViewer +6 -5
  111. data/install/tools/DataViewer.bat +6 -56
  112. data/install/tools/HandbookCreator +6 -5
  113. data/install/tools/HandbookCreator.bat +6 -58
  114. data/install/tools/Launcher +6 -5
  115. data/install/tools/Launcher.bat +6 -56
  116. data/install/tools/LimitsMonitor +6 -5
  117. data/install/tools/LimitsMonitor.bat +6 -56
  118. data/install/tools/OpenGLBuilder +6 -5
  119. data/install/tools/OpenGLBuilder.bat +6 -56
  120. data/install/tools/PacketViewer +6 -5
  121. data/install/tools/PacketViewer.bat +6 -56
  122. data/install/tools/Replay +6 -5
  123. data/install/tools/Replay.bat +6 -56
  124. data/install/tools/ScriptRunner +6 -5
  125. data/install/tools/ScriptRunner.bat +6 -56
  126. data/install/tools/TableManager +6 -5
  127. data/install/tools/TableManager.bat +6 -56
  128. data/install/tools/TestRunner +6 -5
  129. data/install/tools/TestRunner.bat +6 -56
  130. data/install/tools/TlmExtractor +6 -5
  131. data/install/tools/TlmExtractor.bat +6 -56
  132. data/install/tools/TlmGrapher +6 -5
  133. data/install/tools/TlmGrapher.bat +6 -56
  134. data/install/tools/TlmViewer +6 -5
  135. data/install/tools/TlmViewer.bat +6 -56
  136. data/install/tools/ToolLaunch.bat +63 -0
  137. data/install/tools/mac/CmdExtractor.app/Contents/MacOS/CmdExtractor.rb +6 -5
  138. data/install/tools/mac/CmdExtractor.app/Contents/MacOS/tool_launch.rb +38 -0
  139. data/install/tools/mac/CmdSender.app/Contents/MacOS/CmdSender.rb +6 -5
  140. data/install/tools/mac/CmdSender.app/Contents/MacOS/tool_launch.rb +38 -0
  141. data/install/tools/mac/CmdTlmServer.app/Contents/MacOS/CmdTlmServer.rb +6 -5
  142. data/install/tools/mac/CmdTlmServer.app/Contents/MacOS/tool_launch.rb +38 -0
  143. data/install/tools/mac/DataViewer.app/Contents/MacOS/DataViewer.rb +6 -5
  144. data/install/tools/mac/DataViewer.app/Contents/MacOS/tool_launch.rb +38 -0
  145. data/install/tools/mac/HandbookCreator.app/Contents/MacOS/HandbookCreator.rb +6 -5
  146. data/install/tools/mac/HandbookCreator.app/Contents/MacOS/tool_launch.rb +38 -0
  147. data/install/tools/mac/Launcher.app/Contents/MacOS/Launcher.rb +6 -5
  148. data/install/tools/mac/Launcher.app/Contents/MacOS/tool_launch.rb +38 -0
  149. data/install/tools/mac/LimitsMonitor.app/Contents/MacOS/LimitsMonitor.rb +6 -5
  150. data/install/tools/mac/LimitsMonitor.app/Contents/MacOS/tool_launch.rb +38 -0
  151. data/install/tools/mac/OpenGLBuilder.app/Contents/MacOS/OpenGLBuilder.rb +6 -5
  152. data/install/tools/mac/OpenGLBuilder.app/Contents/MacOS/tool_launch.rb +38 -0
  153. data/install/tools/mac/PacketViewer.app/Contents/MacOS/PacketViewer.rb +6 -5
  154. data/install/tools/mac/PacketViewer.app/Contents/MacOS/tool_launch.rb +38 -0
  155. data/install/tools/mac/Replay.app/Contents/MacOS/Replay.rb +6 -5
  156. data/install/tools/mac/Replay.app/Contents/MacOS/tool_launch.rb +38 -0
  157. data/install/tools/mac/ScriptRunner.app/Contents/MacOS/ScriptRunner.rb +6 -5
  158. data/install/tools/mac/ScriptRunner.app/Contents/MacOS/tool_launch.rb +38 -0
  159. data/install/tools/mac/TableManager.app/Contents/MacOS/TableManager.rb +6 -5
  160. data/install/tools/mac/TableManager.app/Contents/MacOS/tool_launch.rb +38 -0
  161. data/install/tools/mac/TestRunner.app/Contents/MacOS/TestRunner.rb +6 -5
  162. data/install/tools/mac/TestRunner.app/Contents/MacOS/tool_launch.rb +38 -0
  163. data/install/tools/mac/TlmExtractor.app/Contents/MacOS/TlmExtractor.rb +6 -5
  164. data/install/tools/mac/TlmExtractor.app/Contents/MacOS/tool_launch.rb +38 -0
  165. data/install/tools/mac/TlmGrapher.app/Contents/MacOS/TlmGrapher.rb +6 -5
  166. data/install/tools/mac/TlmGrapher.app/Contents/MacOS/tool_launch.rb +38 -0
  167. data/install/tools/mac/TlmViewer.app/Contents/MacOS/TlmViewer.rb +6 -5
  168. data/install/tools/mac/TlmViewer.app/Contents/MacOS/tool_launch.rb +38 -0
  169. data/install/tools/tool_launch.rb +38 -0
  170. data/lib/cosmos/core_ext/string.rb +3 -2
  171. data/lib/cosmos/gui/dialogs/about_dialog.rb +3 -7
  172. data/lib/cosmos/gui/dialogs/find_replace_dialog.rb +200 -136
  173. data/lib/cosmos/gui/opengl/gl_viewer.rb +8 -8
  174. data/lib/cosmos/gui/qt.rb +56 -27
  175. data/lib/cosmos/gui/qt_tool.rb +3 -1
  176. data/lib/cosmos/gui/text/ruby_editor.rb +130 -110
  177. data/lib/cosmos/gui/utilities/script_module_gui.rb +150 -4
  178. data/lib/cosmos/io/json_drb.rb +1 -1
  179. data/lib/cosmos/io/win32_serial_driver.rb +2 -4
  180. data/lib/cosmos/packet_logs/ccsds_log_reader.rb +1 -0
  181. data/lib/cosmos/packet_logs/packet_log_reader.rb +13 -4
  182. data/lib/cosmos/packets/limits.rb +6 -3
  183. data/lib/cosmos/packets/telemetry.rb +34 -3
  184. data/lib/cosmos/processors/new_packet_log_processor.rb +1 -1
  185. data/lib/cosmos/script/commands.rb +20 -2
  186. data/lib/cosmos/script/extract.rb +11 -3
  187. data/lib/cosmos/script/limits.rb +6 -0
  188. data/lib/cosmos/script/scripting.rb +17 -9
  189. data/lib/cosmos/system/system.rb +73 -17
  190. data/lib/cosmos/system/target.rb +10 -5
  191. data/lib/cosmos/tools/cmd_extractor/cmd_extractor.rb +1 -0
  192. data/lib/cosmos/tools/cmd_tlm_server/api.rb +95 -0
  193. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +8 -4
  194. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +55 -0
  195. data/lib/cosmos/tools/data_viewer/data_viewer.rb +5 -12
  196. data/lib/cosmos/tools/data_viewer/data_viewer_component.rb +14 -48
  197. data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +1 -5
  198. data/lib/cosmos/tools/launcher/launcher.rb +4 -0
  199. data/lib/cosmos/tools/launcher/launcher_config.rb +50 -0
  200. data/lib/cosmos/tools/launcher/launcher_tool.rb +21 -9
  201. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +607 -566
  202. data/lib/cosmos/tools/replay/replay.rb +51 -45
  203. data/lib/cosmos/tools/script_runner/script_runner.rb +13 -5
  204. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +8 -109
  205. data/lib/cosmos/tools/test_runner/test.rb +65 -6
  206. data/lib/cosmos/tools/test_runner/test_runner.rb +4 -4
  207. data/lib/cosmos/tools/tlm_extractor/tlm_extractor.rb +5 -0
  208. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_logfile_thread.rb +3 -0
  209. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +3 -2
  210. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +9 -6
  211. data/lib/cosmos/tools/tlm_viewer/widgets/array_widget.rb +1 -5
  212. data/lib/cosmos/tools/tlm_viewer/widgets/block_widget.rb +1 -5
  213. data/lib/cosmos/top_level.rb +86 -3
  214. data/lib/cosmos/version.rb +5 -5
  215. data/lib/cosmos/win32/win32_main.rb +7 -1
  216. data/spec/packet_logs/packet_log_reader_spec.rb +67 -7
  217. data/spec/packets/limits_spec.rb +19 -1
  218. data/spec/packets/telemetry_spec.rb +44 -1
  219. data/spec/script/commands_spec.rb +14 -0
  220. data/spec/script/scripting_spec.rb +5 -1
  221. data/spec/script/telemetry_spec.rb +38 -3
  222. data/spec/system/system_spec.rb +24 -4
  223. data/spec/tools/cmd_tlm_server/api_spec.rb +30 -0
  224. data/test/benchmarks/gsub_benchmark.rb +42 -4
  225. data/test/benchmarks/is_a_benchmark.rb +34 -0
  226. data/test/performance/config/data/crc.txt +161 -171
  227. data/test/performance/config/system/system_packets.txt +10 -10
  228. data/test/performance/config/system/system_threads.txt +30 -30
  229. data/test/performance/config/targets/COSMOS/cmd_tlm/cosmos_server_cmds.txt +7 -2
  230. data/test/performance/config/targets/PACKET/cmd_tlm/packet_cmds.txt +20 -0
  231. data/test/performance/config/targets/PACKET/cmd_tlm/packet_tlm.txt +98 -0
  232. data/test/performance/config/targets/{EXAMPLE → PACKET}/cmd_tlm_server.txt +2 -2
  233. data/test/performance/config/targets/PACKET/lib/packet_interface.rb +22 -0
  234. data/test/performance/config/targets/PACKET/lib/packet_limits_response.rb +24 -0
  235. data/test/performance/config/targets/PACKET/screens/status.txt +25 -0
  236. data/test/performance/config/targets/PACKET/target.txt +28 -0
  237. data/test/performance/config/targets/{EXAMPLE/cmd_tlm/example_cmds.txt → THREAD/cmd_tlm/thread_cmds.txt} +1 -1
  238. data/test/performance/config/targets/{EXAMPLE/cmd_tlm/example_tlm.txt → THREAD/cmd_tlm/thread_tlm.txt} +1 -1
  239. data/test/performance/config/targets/THREAD/cmd_tlm_server.txt +6 -0
  240. data/test/performance/config/targets/{EXAMPLE/lib/example_interface.rb → THREAD/lib/thread_interface.rb} +1 -1
  241. data/test/performance/config/targets/THREAD/screens/status.txt +25 -0
  242. data/test/performance/config/targets/{EXAMPLE → THREAD}/target.txt +0 -0
  243. data/test/performance/config/tools/cmd_tlm_server/cmd_tlm_server_packets.txt +24 -30
  244. data/test/performance/config/tools/cmd_tlm_server/cmd_tlm_server_threads.txt +31 -31
  245. data/test/performance/config/tools/launcher/launcher_packets.txt +16 -11
  246. data/test/performance/config/tools/launcher/launcher_threads.txt +41 -35
  247. data/test/performance/config/tools/tlm_grapher/tlm_grapher.txt +204 -0
  248. data/test/performance/config/tools/tlm_viewer/tlm_viewer.txt +10 -38
  249. data/test/performance/lib/packet_target.rb +126 -0
  250. data/test/performance/lib/{example_target.rb → thread_target.rb} +9 -9
  251. data/test/performance/tools/CmdTlmServerMemProf +1 -1
  252. data/test/performance/tools/{ExampleTarget → PacketTarget} +2 -2
  253. data/test/performance/tools/{ExampleTarget.bat → PacketTarget.bat} +0 -0
  254. data/test/performance/tools/ThreadTarget +14 -0
  255. data/test/performance/tools/ThreadTarget.bat +59 -0
  256. data/test/performance/tools/TlmGrapherMemProf +1 -1
  257. data/test/performance/tools/TlmViewerMemProf +19 -0
  258. data/{autohotkey/tools/Replay.bat → test/performance/tools/TlmViewerMemProf.bat} +1 -1
  259. metadata +107 -55
  260. data/test/performance/lib/example_background_task.rb +0 -57
  261. data/test/performance/lib/scpi_target.rb +0 -74
@@ -180,28 +180,20 @@ module Cosmos
180
180
  end
181
181
 
182
182
  def find
183
- unless @find_dialog
184
- @find_dialog = FindReplaceDialog.new(@script, false)
185
- @find_dialog.connect(SIGNAL('find_next()')) do
186
- current_component do |component|
187
- component.find(@find_dialog)
188
- end
189
- end
183
+ current_component do |component|
184
+ FindReplaceDialog.show_find(component.text)
190
185
  end
191
- @find_dialog.show
192
- @find_dialog.raise
193
- @find_dialog.activateWindow
194
186
  end
195
187
 
196
188
  def find_next
197
189
  current_component do |component|
198
- component.find_next(@find_dialog) if @find_dialog
190
+ FindReplaceDialog.find_next(component.text)
199
191
  end
200
192
  end
201
193
 
202
194
  def find_previous
203
195
  current_component do |component|
204
- component.find_previous(@find_dialog) if @find_dialog
196
+ FindReplaceDialog.find_previous(component.text)
205
197
  end
206
198
  end
207
199
 
@@ -435,6 +427,7 @@ module Cosmos
435
427
  file_size = File.size(filename).to_f
436
428
  dialog.append_text("Processing: #{filename}")
437
429
 
430
+ Cosmos.check_log_configuration(@packet_log_reader, filename)
438
431
  @packet_log_reader.each(filename, true, @time_start, @time_end) do |packet|
439
432
  break if @cancel_progress
440
433
  progress = @packet_log_reader.bytes_read.to_f / file_size
@@ -15,8 +15,12 @@ module Cosmos
15
15
  class DataViewerComponent < Qt::Widget
16
16
  attr_reader :tab_name
17
17
  attr_reader :packets
18
+ attr_reader :text
18
19
 
19
- # Initialize the Data Viewer Component
20
+ # Create a component to go inside the DataViewer
21
+ #
22
+ # @param parent [Qt::Widget] Parent widget
23
+ # @param tab_name [String] Name of the tab which displays this widget
20
24
  def initialize(parent, tab_name)
21
25
  super(parent)
22
26
  @tab_name = tab_name
@@ -28,6 +32,9 @@ module Cosmos
28
32
  end
29
33
 
30
34
  # Adds a packet to the list of packets this components processes
35
+ #
36
+ # @param target_name [String] Name of the target
37
+ # @param packet_name [String] Name of the packet
31
38
  def add_packet(target_name, packet_name)
32
39
  @packets << [target_name, packet_name]
33
40
  end
@@ -39,11 +46,7 @@ module Cosmos
39
46
  @text = Qt::PlainTextEdit.new
40
47
  @text.setReadOnly(true)
41
48
  @text.setMaximumBlockCount(@max_block_count)
42
- if Kernel.is_windows?
43
- @text.font = Cosmos.getFont("courier", 9)
44
- else
45
- @text.font = Cosmos.getFont("courier", 12)
46
- end
49
+ @text.font = Cosmos.get_default_small_font
47
50
  @text.setWordWrapMode(Qt::TextOption::NoWrap)
48
51
  @top_layout.addWidget(@text)
49
52
 
@@ -93,49 +96,12 @@ module Cosmos
93
96
  @text.setPlainText("")
94
97
  end
95
98
 
96
- def find(dialog)
97
- found = @text.find(dialog.find_text, dialog.find_flags)
98
- if not found and dialog.wrap_around?
99
- cursor = @text.textCursor
100
- if dialog.find_up?
101
- cursor.movePosition(Qt::TextCursor::End)
102
- else
103
- cursor.movePosition(Qt::TextCursor::Start)
104
- end
105
- @text.setTextCursor(cursor)
106
- @text.find(dialog.find_text, dialog.find_flags)
107
- end
108
- end
109
-
110
- def find_next(dialog)
111
- flags = dialog.find_flags
112
- flags &= ~Qt::TextDocument::FindBackward.to_i
113
- found = @text.find(dialog.find_text, flags)
114
- if not found and dialog.wrap_around?
115
- cursor = @text.textCursor
116
- cursor.movePosition(Qt::TextCursor::Start)
117
- @text.setTextCursor(cursor)
118
- @text.find(dialog.find_text, flags)
119
- end
120
- end
121
-
122
- def find_previous(dialog)
123
- flags = dialog.find_flags
124
- flags |= Qt::TextDocument::FindBackward.to_i
125
- found = @text.find(dialog.find_text, flags)
126
- if not found and dialog.wrap_around?
127
- cursor = @text.textCursor
128
- cursor.movePosition(Qt::TextCursor::End)
129
- @text.setTextCursor(cursor)
130
- @text.find(dialog.find_text, flags)
131
- end
132
- end
133
-
134
99
  def showEvent(event)
135
- # When the tab is shown we want to ensure the scroll bar is at the maximum to allow
136
- # the PlainTextArea to automatically hold the scroll at the bottom of the display while
137
- # appending things. If this is not done, switching tabs will cause the scroll bar to "stick"
138
- # and not stay at the bottom with the newest text.
100
+ # When the tab is shown we want to ensure the scroll bar is at the
101
+ # maximum to allow the PlainTextArea to automatically hold the scroll
102
+ # at the bottom of the display while appending things.
103
+ # If this is not done, switching tabs will cause the scroll bar
104
+ # to "stick" and not stay at the bottom with the newest text.
139
105
  @timer.start(100)
140
106
  end
141
107
 
@@ -83,11 +83,7 @@ module Cosmos
83
83
  if @pdf
84
84
  if progress_dialog
85
85
  Qt.execute_in_main_thread(true) do
86
- if Kernel.is_windows?
87
- progress_dialog.set_text_font(Cosmos.getFont("courier",10))
88
- else
89
- progress_dialog.set_text_font(Cosmos.getFont("courier",14))
90
- end
86
+ progress_dialog.set_text_font(Cosmos.get_default_font)
91
87
  end
92
88
  end
93
89
  Cosmos.set_working_dir do
@@ -23,6 +23,10 @@ module Cosmos
23
23
 
24
24
  def initialize(options)
25
25
  super(options) # MUST BE FIRST - All code before super is executed twice in RubyQt Based classes
26
+
27
+ # Set environment variable of COSMOS_USERPATH so that all launched apps know where to find the configuration
28
+ ENV['COSMOS_USERPATH'] = Cosmos::USERPATH
29
+
26
30
  Cosmos.load_cosmos_icon("launcher.png")
27
31
  layout.setSizeConstraint(Qt::Layout::SetFixedSize)
28
32
 
@@ -11,6 +11,7 @@
11
11
  require 'cosmos'
12
12
  require 'cosmos/config/config_parser'
13
13
  require 'ostruct'
14
+ require 'bundler'
14
15
 
15
16
  module Cosmos
16
17
 
@@ -62,6 +63,9 @@ module Cosmos
62
63
  # Handle each keyword
63
64
  case keyword
64
65
 
66
+ when 'AUTO_GEM_TOOLS'
67
+ parse_gem_tool(parser)
68
+
65
69
  when 'TOOL'
66
70
  parse_tool(parser, params, multitool)
67
71
 
@@ -140,6 +144,25 @@ module Cosmos
140
144
 
141
145
  protected
142
146
 
147
+ def parse_gem_tool(parser)
148
+ parser.verify_num_parameters(0, 0, parser.keyword)
149
+ Bundler.load.specs.each do |spec|
150
+ spec_name_split = spec.name.split('-')
151
+ if spec_name_split.length > 1 and spec_name_split[0] == 'cosmos'
152
+ # Filter to just tools and not targets
153
+ if File.exist?(File.join(spec.gem_dir, 'tools'))
154
+ Dir[File.join(spec.gem_dir, 'tools', '*')].each do |filename|
155
+ if File.extname(filename) == ''
156
+ @items << [:TOOL, File.basename(filename), format_shell_command(parser, "LAUNCH_GEM #{File.basename(filename)}"), true, File.basename(filename).class_name_to_filename(false) + '.png', nil]
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ rescue Bundler::GemfileNotFound
163
+ # No Gemfile - so no gem based tools
164
+ end
165
+
143
166
  def parse_tool(parser, params, multitool)
144
167
  if multitool
145
168
  parser.verify_num_parameters(1, 1, "TOOL <Shell command>")
@@ -165,6 +188,8 @@ module Cosmos
165
188
  formatted_command = parse_launch(shell_command)
166
189
  when 'LAUNCH_TERMINAL'
167
190
  formatted_command = parse_launch_terminal(shell_command)
191
+ when 'LAUNCH_GEM'
192
+ formatted_command = parse_launch_gem(shell_command)
168
193
  else
169
194
  # Nothing to do if they aren't using our keywords
170
195
  formatted_command = shell_command
@@ -201,6 +226,31 @@ module Cosmos
201
226
  formatted
202
227
  end
203
228
 
229
+ def parse_launch_gem(command)
230
+ split = command.split
231
+
232
+ # Find the gem with this file
233
+ begin
234
+ Bundler.load.specs.each do |spec|
235
+ spec_name_split = spec.name.split('-')
236
+ if spec_name_split.length > 1 and spec_name_split[0] == 'cosmos'
237
+ # Filter to just tools and not targets
238
+ if File.exist?(File.join(spec.gem_dir, 'tools', split[1]))
239
+ if Kernel.is_mac? and File.exist?(File.join(USERPATH, 'tools', 'mac'))
240
+ return "open #{spec.gem_dir}/tools/mac/#{split[1]}.app --args #{split[2..-1].join(' ')}".strip
241
+ else
242
+ return "RUBYW #{spec.gem_dir}/tools/#{split[1]} #{split[2..-1].join(' ')}".strip
243
+ end
244
+ end
245
+ end
246
+ end
247
+ rescue Bundler::GemfileNotFound
248
+ # No Gemfile - so no gem based tools
249
+ end
250
+
251
+ raise "Could not find gem containing tool: #{split[1]} - Make sure the appropriate gem is in your Gemfile"
252
+ end
253
+
204
254
  end # class LauncherConfig
205
255
 
206
256
  end # module Cosmos
@@ -12,7 +12,6 @@ require 'cosmos'
12
12
  require 'cosmos/gui/qt'
13
13
 
14
14
  module Cosmos
15
-
16
15
  class LauncherTool < Qt::Object
17
16
  slots 'button_clicked()'
18
17
 
@@ -26,7 +25,7 @@ module Cosmos
26
25
 
27
26
  def button_clicked
28
27
  if @variable_parameters
29
- parameters = parameters_dialog()
28
+ parameters = parameters_dialog
30
29
  if parameters
31
30
  if @capture_io
32
31
  Cosmos.run_process_check_output(@shell_command + ' ' + parameters)
@@ -44,7 +43,7 @@ module Cosmos
44
43
  end
45
44
 
46
45
  def parameters_dialog
47
- dialog = Qt::Dialog.new(self.parent)
46
+ dialog = Qt::Dialog.new(parent)
48
47
  dialog.window_title = "#{@button_text} Options"
49
48
  layout = Qt::VBoxLayout.new
50
49
  dialog.layout = layout
@@ -53,10 +52,24 @@ module Cosmos
53
52
  @variable_parameters.each do |parameter_name, parameter_value|
54
53
  hlayout = Qt::HBoxLayout.new
55
54
  hlayout.addWidget(Qt::Label.new(parameter_name))
56
- line_edit = Qt::LineEdit.new()
57
- line_edit.setText(parameter_value)
58
- hlayout.addWidget(line_edit)
59
- widgets << line_edit
55
+ parameter_options = parameter_value.split('|')
56
+ if parameter_options.length > 1
57
+ combo_box = Qt::ComboBox.new
58
+ combo_box.setEditable(true)
59
+ idx = 0
60
+ parameter_options.each do |option|
61
+ combo_box.insertItem(idx, option)
62
+ idx += 1
63
+ end
64
+ combo_box.setCurrentIndex(0) # Default Option is the first one
65
+ hlayout.addWidget(combo_box)
66
+ widgets << combo_box
67
+ else
68
+ line_edit = Qt::LineEdit.new
69
+ line_edit.setText(parameter_value)
70
+ hlayout.addWidget(line_edit)
71
+ widgets << line_edit
72
+ end
60
73
  layout.addLayout(hlayout)
61
74
  end
62
75
 
@@ -85,7 +98,7 @@ module Cosmos
85
98
  if result == Qt::Dialog::Accepted
86
99
  parameters = ''
87
100
  index = 0
88
- @variable_parameters.each do |parameter_name, parameter_value|
101
+ @variable_parameters.each do |parameter_name, _parameter_value|
89
102
  parameters << parameter_name
90
103
  parameters << ' '
91
104
  parameters << widgets[index].text
@@ -100,5 +113,4 @@ module Cosmos
100
113
  end
101
114
  end
102
115
  end
103
-
104
116
  end
@@ -15,232 +15,460 @@ Cosmos.catch_fatal_exception do
15
15
  require 'cosmos/gui/dialogs/cmd_tlm_raw_dialog'
16
16
  require 'cosmos/script'
17
17
  require 'cosmos/tools/tlm_viewer/widgets/labelvaluelimitsbar_widget'
18
+ require 'cosmos/tools/tlm_viewer/widgets/label_widget'
19
+ require 'pathname'
18
20
  end
19
21
 
22
+ # Extend Array to search for and delete telemetry items.
23
+ # Telemetry items are Arrays of [target name, packet name, item name].
20
24
  class Array
21
25
  def includes_item?(item)
22
- self.each do |target, pkt_name, item_name|
23
- if ((target == item[0]) and (pkt_name == item[1]) and (item_name == item[2]))
24
- return true
25
- end
26
- end
27
- return false
26
+ found, index = find_item(item)
27
+ return found
28
28
  end
29
29
 
30
30
  def delete_item(item)
31
+ found, index = find_item(item)
32
+ self.delete_at(index) if found
33
+ return index
34
+ end
35
+
36
+ private
37
+ def find_item(item)
38
+ found = false
31
39
  index = 0
32
- delete_index = nil
33
- self.each do |target, pkt_name, item_name|
34
- if ((target == item[0]) and (pkt_name == item[1]) and (item_name == item[2]))
35
- delete_index = index
40
+ self.each do |target_name, packet_name, item_name|
41
+ if ((target_name == item[0]) &&
42
+ (packet_name == item[1]) &&
43
+ # If the item name is nil we're dealing with a packet
44
+ (item_name == item[2] || item_name.nil?))
45
+ found = true
36
46
  break
37
47
  end
38
48
  index += 1
39
49
  end
40
-
41
- self.delete_at(delete_index) if delete_index
42
- return delete_index
50
+ return found, index
43
51
  end
44
52
  end
45
53
 
46
54
  module Cosmos
47
55
 
48
- # The LimitsMonitor application displays all the out of limits items
49
- # encountered by the COSMOS server. It provides the ability to ignore and
50
- # restore limits as well as logs all limits events.
51
- class LimitsMonitor < QtTool
52
- slots 'options()'
53
- slots 'reset()'
54
- slots 'handle_clear_ignored_items()'
55
- slots 'handle_view_ignored_items()'
56
- slots 'handle_save_ignored_items()'
57
- slots 'handle_open_ignored_items()'
58
-
59
- # Set up class variables, tab-book with main panel and log panel, menus, and
60
- # status bar on main panel.
61
- #
62
- # @param options [Options] Contains the options for the window.
63
- def initialize(options)
64
- super(options)
65
- Cosmos.load_cosmos_icon("limits_monitor.png")
66
-
67
- @out_of_limits_items = []
56
+ class LimitsItems
57
+ # @return [Array<String,String,String>] Target name, packet name, item name
58
+ attr_reader :ignored
59
+ # @return [Boolean] Whether the limits items have been fetched from the server
60
+ attr_reader :initialized
61
+
62
+ UNKNOWN_ARRAY = ['UNKNOWN', 'UNKNOWN', nil]
63
+
64
+ # @param new_item_callback [Proc] Method to create a new item in the GUI
65
+ # @param update_item_callback [Proc] Method to update an item in the GUI
66
+ # @param clear_items_callback [Proc] Method to clear all items in the GUI
67
+ def initialize(new_item_callback, update_item_callback, clear_items_callback)
68
+ @new_item_callback = new_item_callback
69
+ @update_item_callback = update_item_callback
70
+ @clear_items_callback = clear_items_callback
71
+ @ignored = []
72
+ @items = {}
73
+ @out_of_limits = []
68
74
  @queue_id = nil
69
- @limits_set = nil
70
- @widgets = []
71
- @widget_hframes = []
72
- @value_limits_set = nil
73
- @items = []
74
- @new_items = []
75
+ @limits_set = :DEFAULT
76
+ request_reset()
77
+ end
78
+
79
+ # Request that the limits items be refreshed from the server
80
+ def request_reset
75
81
  @initialized = false
76
- @overall_limits_state = :STALE
77
- @ignored_items = []
78
- @ignored_filename = nil
79
- @colorblind = false
80
- @new_widgets = []
81
- @buttons = []
82
- @cancel_thread = false
83
- @limits_sleeper = Sleeper.new
84
- @value_sleeper = Sleeper.new
82
+ end
85
83
 
86
- statusBar.showMessage(tr(""))
84
+ # Ignore an item. Don't display it in the GUI if it goes out of limits
85
+ # and don't have it count towards the overall limit state. Still display
86
+ # its limits transitions in the log.
87
+ #
88
+ # @param item [Array<String,String,String>] Target name, packet name,
89
+ # item name to ignore
90
+ def ignore(item)
91
+ index = @out_of_limits.delete_item(item)
92
+ @items.delete("#{item[0]} #{item[1]} #{item[2]}") if index
93
+ unless @ignored.includes_item?(item)
94
+ @ignored << item
95
+ end
96
+ end
87
97
 
88
- initialize_actions()
89
- initialize_menus()
90
- complete_initialize()
98
+ # Remove an item from the ignored list to have it be displayed and
99
+ # count towards the overall limits state.
100
+ #
101
+ # @param item [Array<String,String,String> Target name, packet name,
102
+ # item name to remove from ignored list
103
+ def remove_ignored(item)
104
+ index = @ignored.delete_item(item)
105
+ if index
106
+ # If we deleted a packet we need to recalculate the stale packets
107
+ if item[2].empty?
108
+ get_stale(true).each do |target, packet|
109
+ stale_packet(target, packet)
110
+ end
111
+ # We deleted an item so get all the current out of limit items
112
+ else
113
+ get_out_of_limits().each do |target, packet, item, state|
114
+ limits_change(target, packet, item, state)
115
+ end
116
+ end
117
+ end
118
+ rescue DRb::DRbConnError
119
+ # Do nothing
120
+ end
91
121
 
92
- @tabbook = Qt::TabWidget.new(self)
93
- setCentralWidget(@tabbook)
94
- @widget = Qt::Widget.new
95
- @layout = Qt::VBoxLayout.new(@widget)
122
+ # @return [Boolean] Whether there are any items being ignored
123
+ def ignored_items?
124
+ !@ignored.empty?
125
+ end
96
126
 
97
- @monitored_state_text_field = Qt::LineEdit.new(self)
98
- @monitored_state_text_field.setText('Stale')
99
- @monitored_state_text_field.setAlignment(Qt::AlignCenter)
100
- @monitored_state_text_field.setReadOnly(true)
101
- @palette = Qt::Palette.new()
102
- @palette.setColor(Qt::Palette::Base, Qt::Color.new(255,0,255))
103
- @monitored_state_text_field.setPalette(@palette)
104
- @state_label = Qt::Label.new('Monitored Limits State: ')
127
+ # @return [Symbol] The overall limits state. Returns :STALE if there
128
+ # is no connection to the server.
129
+ def overall_state
130
+ get_overall_limits_state(@ignored)
131
+ rescue DRb::DRbConnError
132
+ :STALE
133
+ end
105
134
 
106
- @monitored_state_frame = Qt::HBoxLayout.new
107
- @monitored_state_frame.addWidget(@state_label)
108
- @monitored_state_frame.addWidget(@monitored_state_text_field)
109
- label = Qt::Label.new
110
- filename = File.join(::Cosmos::PATH, 'data', 'spinner.gif')
111
- movie = Qt::Movie.new(filename)
112
- label.setMovie(movie)
113
- movie.start
114
- @monitored_state_frame.addWidget(label)
135
+ # Calls get_limits_event to process all the server limits events that
136
+ # were subscribed to. This method should be called continuously until
137
+ # it returns nil which indicates no more events.
138
+ #
139
+ # @return [Array<String,Symbol] String describing the event and a symbol
140
+ # indicating how the event string should be colored (:BLACK, :BLUE,
141
+ # :GREEN, :YELLOW, or :RED)
142
+ def process_events
143
+ result = nil
144
+ type = nil
145
+ data = nil
146
+ begin
147
+ reset() unless @initialized
148
+ # Get events non-blocking which is why we rescue ThreadError
149
+ type, data = get_limits_event(@queue_id, true)
150
+ rescue ThreadError
151
+ # Do nothing (nominal exception if there are no events)
152
+ rescue DRb::DRbConnError
153
+ # The server is down so request a reset
154
+ request_reset()
155
+ end
156
+ return result unless type
157
+
158
+ case type
159
+ when :LIMITS_CHANGE
160
+ # The most common event: target, packet, item, state
161
+ result = limits_change(data[0], data[1], data[2], data[4])
162
+
163
+ when :LIMITS_SET
164
+ # Check if the overall limits set changed. If so we need to reset
165
+ # to incorporate all the new limits.
166
+ if @limits_set != data
167
+ request_reset()
168
+ result = ["INFO: Limits Set Changed to: #{data}\n", :BLACK]
169
+ end
115
170
 
116
- @monitored_state_frame.setAlignment(Qt::AlignTop)
171
+ when :LIMITS_SETTINGS
172
+ # The limits settings for an individual item changed. Set our local tool
173
+ # knowledge of the limits to match the server.
174
+ begin
175
+ System.limits.set(data[0], data[1], data[2], data[6], data[7], data[8], data[9], data[10], data[11], data[3], data[4], data[5])
176
+ result = ["INFO: Limits Settings Changed: #{data}\n", :BLACK]
177
+ rescue
178
+ # This can fail if we missed setting the DEFAULT limits set earlier
179
+ end
117
180
 
118
- @scroll = Qt::ScrollArea.new
119
- @scroll_widget = Qt::Widget.new
120
- @scroll_layout = Qt::VBoxLayout.new(@scroll_widget)
121
- @scroll_layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
181
+ when :STALE_PACKET
182
+ # A packet has gone stale: target, packet
183
+ result = stale_packet(data[0], data[1])
184
+ end
185
+ result
186
+ end
122
187
 
123
- @scroll.setWidget(@scroll_widget)
188
+ # Update the values for all the out of limits items being tracked.
189
+ def update_values
190
+ # Reject any out of limits packets as they don't have values
191
+ items = @out_of_limits.reject {|item| item[2].nil? }
124
192
 
125
- @layout.addLayout(@monitored_state_frame)
126
- @layout.addWidget(@scroll)
193
+ values, limits_states, limits_settings, limits_set = get_tlm_values(items, :WITH_UNITS)
194
+ index = 0
195
+ items.each do |target_name, packet_name, item_name|
196
+ begin
197
+ # Update the limits settings each time we update values
198
+ # to stay in sync with the Server. Responding to :LIMITS_SETTINGS
199
+ # events isn't enough since we don't get those upon startup.
200
+ System.limits.set(target_name, packet_name, item_name,
201
+ limits_settings[index][0], limits_settings[index][1],
202
+ limits_settings[index][2], limits_settings[index][3],
203
+ limits_settings[index][4], limits_settings[index][5],
204
+ limits_set) if limits_settings[index]
205
+ rescue
206
+ # This can fail if we missed setting the DEFAULT limits set earlier
207
+ end
208
+ name = "#{target_name} #{packet_name} #{item_name}"
209
+ @update_item_callback.call(@items[name], values[index], limits_states[index], limits_set)
210
+ index += 1
211
+ end
212
+ rescue DRb::DRbConnError
213
+ # Do nothing
214
+ end
127
215
 
128
- @log_output = Qt::PlainTextEdit.new
129
- @log_output.setReadOnly(true)
130
- @log_output.setMaximumBlockCount(100)
216
+ # Load a new configuration of ignored items and packets and reset
217
+ #
218
+ # @param config_file [String] Configuration file base name which will be
219
+ # expanded to find a file in the config/tools/limits_monitor dir.
220
+ # @return [String] Message indicating success or fail
221
+ def open_config(filename)
222
+ return "" unless filename
223
+
224
+ unless Pathname.new(filename).absolute?
225
+ filename = File.join(::Cosmos::USERPATH, 'config', 'tools', 'limits_monitor', filename)
226
+ end
227
+ return "Configuration file #{filename} not found!" unless File.exist?(filename)
131
228
 
132
- @tabbook.addTab(@widget, "Limits")
133
- @tabbook.addTab(@log_output, "Log")
229
+ @ignored = []
230
+ begin
231
+ parser = ConfigParser.new
232
+ parser.parse_file(filename) do |keyword, params|
233
+ case keyword
234
+ # TODO: Eventually we can deprecate 'IGNORE' in favor
235
+ # of 'IGNORE_ITEM' now that we also have 'IGNORE_PACKET'
236
+ when 'IGNORE', 'IGNORE_ITEM'
237
+ @ignored << ([params[0], params[1], params[2]])
238
+ when 'IGNORE_PACKET'
239
+ @ignored << ([params[0], params[1], nil])
240
+ end
241
+ end
242
+ result = "#{filename} loaded. "
243
+ result << "Warning: Some items ignored" if ignored_items?
244
+ rescue => e
245
+ result = "Error loading configuration : #{e.message}"
246
+ end
247
+ # Since we may have loaded new ignored items we need to reset
248
+ request_reset()
249
+ result
250
+ end
134
251
 
135
- # Process config file if present
136
- process_config(File.join(::Cosmos::USERPATH, 'config', 'tools', 'limits_monitor', options.config_file)) if options.config_file
252
+ # Save the current configuration of ignored items and packets.
253
+ #
254
+ # @param config_file [String] Configuration file to save.
255
+ # @return [String] Message indicating success or fail
256
+ def save_config(filename)
257
+ begin
258
+ File.open(filename, "w") do |file|
259
+ @ignored.each do |target, pkt_name, item_name|
260
+ if item_name
261
+ file.puts("IGNORE_ITEM #{target} #{pkt_name} #{item_name}")
262
+ else
263
+ file.puts("IGNORE_PACKET #{target} #{pkt_name}")
264
+ end
265
+ end
266
+ end
267
+ result = "#{filename} saved"
268
+ rescue => e
269
+ result = "Error saving configuration : #{e.message}"
270
+ end
271
+ result
272
+ end
137
273
 
138
- # Start thread to monitor limits
139
- value_thread()
140
- limits_thread()
141
- end # initialize
274
+ private
142
275
 
143
- # Slot to add items to the front panel when they are out of limits.
276
+ # Clear all tracked out of limits items and resubscribe to the server
277
+ # limits events. Clear the GUI and re-create all out of limits items
278
+ # and stale packets.
144
279
  #
145
- # @param target_name [String] Target name of out of limits item.
146
- # @param packet_name [String] Packet name of out of limits item.
147
- # @param item_name [String] Item name of out of limits item.
148
- def add_items(target_name, packet_name, item_name)
149
- Qt.execute_in_main_thread(true) do
150
- hlayout = Qt::HBoxLayout.new
151
- hlayout.setSpacing(0)
152
- hlayout.setContentsMargins(0,0,0,0)
153
- @scroll_layout.addLayout(hlayout)
280
+ # Note this method can raise a DRb::DRbConnError error!
281
+ def reset
282
+ @items = {}
283
+ @out_of_limits = []
284
+ @limits_set = get_limits_set()
285
+ unsubscribe_limits_events(@queue_id) if @queue_id
286
+ @queue_id = subscribe_limits_events(100000)
287
+ @clear_items_callback.call
288
+ get_out_of_limits().each do |target, packet, item, state|
289
+ limits_change(target, packet, item, state)
290
+ end
291
+ get_stale(true).each do |target, packet|
292
+ stale_packet(target, packet)
293
+ end
154
294
 
155
- item = [target_name, packet_name, item_name]
156
- new_widget = LabelvaluelimitsbarWidget.new(hlayout, target_name, packet_name, item_name)
157
- new_widget.set_setting('COLORBLIND', [@colorblind])
158
- new_widget.process_settings
295
+ @initialized = true
296
+ end
159
297
 
160
- ignore_button = Qt::PushButton.new('Ignore')
161
- ignore_button.connect(SIGNAL('clicked()')) do
162
- ignore_item(item)
163
- end
298
+ # Process a limits_change event by recoloring out of limits events
299
+ # and creating a log message.
300
+ def limits_change(target_name, packet_name, item_name, state)
301
+ message = ''
302
+ color = :BLACK
303
+ item = [target_name, packet_name, item_name]
304
+
305
+ case state
306
+ when :YELLOW, :YELLOW_HIGH, :YELLOW_LOW
307
+ message << "WARN: "
308
+ color = :YELLOW
309
+ out_of_limit(item)
310
+ when :RED, :RED_HIGH, :RED_LOW
311
+ message << "ERROR: "
312
+ color = :RED
313
+ out_of_limit(item)
314
+ when :GREEN, :GREEN_HIGH, :GREEN_LOW
315
+ message << "INFO: "
316
+ color = :GREEN
317
+ when :BLUE
318
+ message << "INFO: "
319
+ color = :BLUE
320
+ end
321
+ value = tlm(target_name, packet_name, item_name)
322
+ message << "#{target_name} #{packet_name} #{item_name} = #{value} is #{state}\n"
323
+ [message, color]
324
+ end
164
325
 
165
- hlayout.addWidget(ignore_button)
326
+ # Record the stale packet and generate a log message
327
+ def stale_packet(target_name, packet_name)
328
+ out_of_limit([target_name, packet_name, nil])
329
+ return ["INFO: Packet #{target_name} #{packet_name} is STALE\n", :BLACK]
330
+ end
166
331
 
167
- @widget_hframes << hlayout
168
- @new_widgets << new_widget
169
- @widgets << new_widget
170
- @buttons << ignore_button
332
+ # Record an out of limits item and call the new item callback.
333
+ # Existing out of limits and ignored items are not recorded.
334
+ def out_of_limit(item)
335
+ unless (@out_of_limits.includes_item?(item) || @ignored.includes_item?(item) || UNKNOWN_ARRAY.includes_item?(item))
336
+ @out_of_limits << item
337
+ @items["#{item[0]} #{item[1]} #{item[2]}"] = @new_item_callback.call(*item)
171
338
  end
172
339
  end
340
+ end
173
341
 
174
- # Slot to update the log panel with limits change information.
175
- #
176
- # @param to_add [String] Text string with information about which item is out of
177
- # limits, what its value is, and what limit was broken (red_low, yellow_low, etc.)
178
- # @param color [int] Integer representing color of text to add.
179
- def update_log(to_add, color)
180
- return if @cancel_thread
181
- Qt.execute_in_main_thread(true) do
182
- @tf = Qt::TextCharFormat.new
183
- case color
184
- when 0
185
- brush = Cosmos.getBrush(Cosmos::GREEN)
186
- when 1
187
- brush = Cosmos.getBrush(Cosmos::YELLOW)
188
- when 2
189
- brush = Cosmos.getBrush(Cosmos::RED)
190
- when 3
191
- brush = Cosmos.getBrush(Cosmos::BLUE)
342
+ # The LimitsMonitor application displays all the out of limits items
343
+ # encountered by the COSMOS server. It provides the ability to ignore and
344
+ # restore limits as well as logs all limits events.
345
+ class LimitsMonitor < QtTool
346
+ # LimitsWidget displays either a stale packet using the Label widget
347
+ # or more commonly an out of limits item using the Labelvaluelimitsbar
348
+ # Widget.
349
+ class LimitsWidget < Qt::Widget
350
+ # @return [Widget] The widget which displays the value
351
+ attr_accessor :value
352
+
353
+ # @parent [Qt::Widget] Parent widget (the LimitsMonitor tool)
354
+ # @target_name [String] Target name
355
+ # @packet_name [String] Packet name
356
+ # @item_name [String] Telemetry item name (nil for stale packets)
357
+ def initialize(parent, target_name, packet_name, item_name)
358
+ super(parent)
359
+ @layout = Qt::HBoxLayout.new
360
+ @layout.setSpacing(0)
361
+ @layout.setContentsMargins(0,0,0,0)
362
+ setLayout(@layout)
363
+
364
+ item = [target_name, packet_name, item_name]
365
+ if item_name
366
+ @value = LabelvaluelimitsbarWidget.new(@layout, target_name, packet_name, item_name)
367
+ @value.set_setting('COLORBLIND', [@colorblind])
368
+ @value.process_settings
192
369
  else
193
- brush = Cosmos.getBrush(Cosmos::BLACK)
370
+ @value = LabelWidget.new(layout, "#{target_name} #{packet_name} is STALE")
371
+ end
372
+
373
+ @ignore_button = Qt::PushButton.new('Ignore')
374
+ @ignore_button.connect(SIGNAL('clicked()')) { parent.ignore(self, item) }
375
+ @layout.addWidget(@ignore_button)
376
+ end
377
+
378
+ # Update the widget's value, limits_state, and limits_set
379
+ def set_values(value, limits_state, limits_set)
380
+ if LabelvaluelimitsbarWidget === @value
381
+ @value.value = value
382
+ @value.limits_state = limits_state
383
+ @value.limits_set = limits_set
384
+ end
385
+ end
386
+
387
+ # Enable or disable Colorblind mode
388
+ def set_colorblind(enabled)
389
+ if LabelvaluelimitsbarWidget === @value
390
+ @value.set_setting('COLORBLIND', [enabled])
391
+ @value.process_settings
194
392
  end
195
- @tf.setForeground(brush)
196
- @log_output.setCurrentCharFormat(@tf)
197
- @log_output.appendPlainText(to_add.chomp)
198
- @tf.dispose
393
+ end
394
+
395
+ # Dispose of the widget
396
+ def dispose
397
+ @ignore_button.dispose
398
+ @value.dispose
399
+ @layout.dispose
400
+ super()
199
401
  end
200
402
  end
201
403
 
202
- # Sets up the menu bar with selectable options and the signal/slot combinations
203
- # for when those options are selected.
204
- def initialize_menus
205
- # File Menu
206
- @file_menu = menuBar.addMenu(tr('&File'))
404
+ # Create the main application GUI. Start the limits thread which responds to
405
+ # asynchronous limits events from the server and the value thread which
406
+ # polls the server at 1Hz for the out of limits items values.
407
+ #
408
+ # @param options [Options] Contains the options for the window.
409
+ def initialize(options)
410
+ super(options)
411
+ Cosmos.load_cosmos_icon("limits_monitor.png")
412
+
413
+ @cancel_thread = false
414
+ @limits_sleeper = Sleeper.new
415
+ @value_sleeper = Sleeper.new
416
+
417
+ initialize_actions()
418
+ initialize_menus()
419
+ initialize_central_widget()
420
+ complete_initialize()
421
+
422
+ @limits_items = LimitsItems.new(
423
+ method(:new_gui_item), method(:update_gui_item), method(:clear_gui_items))
424
+ result = @limits_items.open_config(options.config_file)
425
+ statusBar.showMessage(tr(result))
426
+
427
+ limits_thread()
428
+ value_thread()
429
+ end
430
+
431
+ # Initialize all the actions in the application Menu
432
+ def initialize_actions
433
+ super
207
434
 
208
435
  @options_action = Qt::Action.new(tr('O&ptions'), self)
209
436
  @options_action.statusTip = tr('Open the options dialog')
210
- connect(@options_action, SIGNAL('triggered()'), self, SLOT('options()'))
437
+ @options_action.connect(SIGNAL('triggered()')) { show_options_dialog() }
211
438
 
212
439
  @reset_action = Qt::Action.new(tr('&Reset'), self)
213
440
  @reset_action_keyseq = Qt::KeySequence.new(tr('Ctrl+R'))
214
441
  @reset_action.shortcut = @reset_action_keyseq
215
442
  @reset_action.statusTip = tr('Reset connection and clear all items. This does not modify the ignored items.')
216
- connect(@reset_action, SIGNAL('triggered()'), self, SLOT('reset()'))
443
+ @reset_action.connect(SIGNAL('triggered()')) { @limits_items.request_reset() }
217
444
 
218
445
  @open_ignored_action = Qt::Action.new(Cosmos.get_icon('open.png'),
219
446
  tr('&Open Config'), self)
220
447
  @open_ignored_action_keyseq = Qt::KeySequence.new(tr('Ctrl+O'))
221
448
  @open_ignored_action.shortcut = @open_ignored_action_keyseq
222
449
  @open_ignored_action.statusTip = tr('Open ignored telemetry items configuration file')
223
- connect(@open_ignored_action, SIGNAL('triggered()'), self, SLOT('handle_open_ignored_items()'))
450
+ @open_ignored_action.connect(SIGNAL('triggered()')) { open_config_file() }
224
451
 
225
452
  @save_ignored_action = Qt::Action.new(Cosmos.get_icon('save.png'),
226
453
  tr('&Save Config'), self)
227
454
  @save_ignored_action_keyseq = Qt::KeySequence.new(tr('Ctrl+S'))
228
455
  @save_ignored_action.shortcut = @save_ignored_action_keyseq
229
456
  @save_ignored_action.statusTip = tr('Save all ignored telemetry items in a configuration file')
230
- connect(@save_ignored_action, SIGNAL('triggered()'), self, SLOT('handle_save_ignored_items()'))
457
+ @save_ignored_action.connect(SIGNAL('triggered()')) { save_config_file() }
231
458
 
232
- @clear_ignored_action = Qt::Action.new(tr('&Clear Ignored'), self)
233
- @clear_ignored_action.statusTip = tr('Clear all ignored telemetry items so they become monitored')
234
- connect(@clear_ignored_action, SIGNAL('triggered()'), self, SLOT('handle_clear_ignored_items()'))
235
-
236
- @view_ignored_action = Qt::Action.new(tr('&View Ignored'), self)
237
- @view_ignored_action.statusTip = tr('View the ignored telemetry items list')
238
- connect(@view_ignored_action, SIGNAL('triggered()'), self, SLOT('handle_view_ignored_items()'))
459
+ @edit_ignored_action = Qt::Action.new(tr('&Edit Ignored'), self)
460
+ @edit_ignored_action_keyseq = Qt::KeySequence.new(tr('Ctrl+E'))
461
+ @edit_ignored_action.shortcut = @edit_ignored_action_keyseq
462
+ @edit_ignored_action.statusTip = tr('Edit the ignored telemetry items list')
463
+ @edit_ignored_action.connect(SIGNAL('triggered()')) { edit_ignored_items() }
464
+ end
239
465
 
466
+ # Initialize the application menu bar options
467
+ def initialize_menus
468
+ @file_menu = menuBar.addMenu(tr('&File'))
240
469
  @file_menu.addAction(@open_ignored_action)
241
470
  @file_menu.addAction(@save_ignored_action)
242
- @file_menu.addAction(@clear_ignored_action)
243
- @file_menu.addAction(@view_ignored_action)
471
+ @file_menu.addAction(@edit_ignored_action)
244
472
  @file_menu.addSeparator()
245
473
  @file_menu.addAction(@reset_action)
246
474
  @file_menu.addAction(@options_action)
@@ -253,29 +481,67 @@ module Cosmos
253
481
  initialize_help_menu()
254
482
  end
255
483
 
256
- # Slot to handle the options menu item when selected.
257
- def options
484
+ # Layout the main GUI tab widget with a view of all the out of limits items
485
+ # in one tab and a log tab showing all limits events.
486
+ def initialize_central_widget
487
+ @tabbook = Qt::TabWidget.new(self)
488
+ setCentralWidget(@tabbook)
489
+ @widget = Qt::Widget.new
490
+ @layout = Qt::VBoxLayout.new(@widget)
491
+
492
+ @monitored_state_text_field = Qt::LineEdit.new(self)
493
+ @monitored_state_text_field.setText('Stale')
494
+ @monitored_state_text_field.setAlignment(Qt::AlignCenter)
495
+ @monitored_state_text_field.setReadOnly(true)
496
+ @palette = Qt::Palette.new()
497
+ @palette.setColor(Qt::Palette::Base, Qt::Color.new(255,0,255))
498
+ @monitored_state_text_field.setPalette(@palette)
499
+ @state_label = Qt::Label.new('Monitored Limits State: ')
500
+
501
+ @monitored_state_frame = Qt::HBoxLayout.new
502
+ @monitored_state_frame.addWidget(@state_label)
503
+ @monitored_state_frame.addWidget(@monitored_state_text_field)
504
+ label = Qt::Label.new
505
+ filename = File.join(::Cosmos::PATH, 'data', 'spinner.gif')
506
+ movie = Qt::Movie.new(filename)
507
+ label.setMovie(movie)
508
+ movie.start
509
+ @monitored_state_frame.addWidget(label)
510
+ @monitored_state_frame.setAlignment(Qt::AlignTop)
511
+ @layout.addLayout(@monitored_state_frame)
512
+
513
+ @scroll = Qt::ScrollArea.new
514
+ @scroll_widget = Qt::Widget.new
515
+ @scroll.setWidget(@scroll_widget)
516
+ @scroll_layout = Qt::VBoxLayout.new(@scroll_widget)
517
+ @scroll_layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
518
+ @layout.addWidget(@scroll)
519
+
520
+ @log_output = Qt::PlainTextEdit.new
521
+ @log_output.setReadOnly(true)
522
+ @log_output.setMaximumBlockCount(100)
523
+
524
+ @tabbook.addTab(@widget, "Limits")
525
+ @tabbook.addTab(@log_output, "Log")
526
+ end
527
+
528
+ def show_options_dialog
258
529
  Qt::Dialog.new(self) do |dialog|
259
530
  dialog.setWindowTitle('Options')
260
531
 
261
532
  colorblind_box = Qt::CheckBox.new('Colorblind Mode Enabled', self)
262
- if (@colorblind)
263
- colorblind_box.setCheckState(Qt::Checked)
264
- end
533
+ colorblind_box.setCheckState(Qt::Checked) if @colorblind
265
534
 
266
535
  ok = Qt::PushButton.new('Ok') do
267
536
  connect(SIGNAL('clicked()')) { dialog.accept }
268
537
  end
269
-
270
538
  cancel = Qt::PushButton.new('Cancel') do
271
539
  connect(SIGNAL('clicked()')) { dialog.reject }
272
540
  end
273
-
274
541
  buttons = Qt::HBoxLayout.new do
275
542
  addWidget(ok)
276
543
  addWidget(cancel)
277
544
  end
278
-
279
545
  dialog.layout = Qt::VBoxLayout.new do
280
546
  addWidget(colorblind_box)
281
547
  addLayout(buttons)
@@ -288,471 +554,247 @@ module Cosmos
288
554
  else
289
555
  @colorblind = false
290
556
  end
291
- @widgets.each do |widget|
292
- widget.set_setting('COLORBLIND', [@colorblind])
293
- widget.process_settings
557
+ (0...@scroll_layout.count).each do |index|
558
+ @scroll_layout.itemAt(index).widget.set_colorblind(@colorblind)
294
559
  end
295
560
  end
296
561
  dialog.dispose
297
562
  end
298
563
  end
299
564
 
300
- # Slot to handle the resent menu item when selected.
301
- def reset
302
- @initialized = false
565
+ # @return [String] Fully qualified path to the configuration file
566
+ def config_path
567
+ # If the config file has been set then just return it
568
+ return @filename if @filename
569
+ # This is the default path to the configuration files
570
+ File.join(::Cosmos::USERPATH, 'config', 'tools', 'limits_monitor', 'limits_monitor.txt')
303
571
  end
304
572
 
305
- # Sets up the thread to monitor for broken limits and add them to the log and
306
- # front panel when found.
307
- def limits_thread
308
- @limits_thread = Thread.new do
309
- begin
310
- while true
311
- break if @cancel_thread
312
- begin
313
- initialized = nil
314
- break if @cancel_thread
315
- Qt.execute_in_main_thread(true) do
316
- initialized = @initialized
317
- end
318
- unless initialized
319
- @limits_set = nil
320
- break if @cancel_thread
321
- Qt.execute_in_main_thread(true) do
322
- @out_of_limits_items = []
323
- end
324
- unsubscribe_limits_events(@queue_id) if @queue_id
325
- @queue_id = nil
326
-
327
- handle_reset()
328
-
329
- # Get the current limits set
330
- @limits_set = get_limits_set()
331
-
332
- # Subscribe to limits notifications
333
- @queue_id = subscribe_limits_events(100000)
334
-
335
- # Get initial list of out of limits items
336
- items = get_out_of_limits()
337
- unless items.empty?
338
- break if @cancel_thread
339
- Qt.execute_in_main_thread(true) do
340
- items.each do |item|
341
- unless @ignored_items.includes_item?(item) and !@items.includes_item?(item)
342
- @new_items << [item[0], item[1], item[2]]
343
- @out_of_limits_items << [item[0], item[1], item[2]]
344
- end
345
- end
346
- handle_new_items()
347
- end
348
- end
349
-
350
- break if @cancel_thread
351
- Qt.execute_in_main_thread(true) do
352
- @initialized = true
353
- if @ignored_items.empty?
354
- statusBar.showMessage(tr(""))
355
- else
356
- statusBar.showMessage('Warning: Some Telemetry Items are Ignored')
357
- end
358
- end
359
-
360
- end
361
-
362
- begin
363
- break if @cancel_thread
364
- type, data = get_limits_event(@queue_id, true)
365
- break if @cancel_thread
366
- rescue ThreadError
367
- break if @cancel_thread
368
- break if @limits_sleeper.sleep(1)
369
- next
370
- end
371
-
372
- break if @cancel_thread
373
-
374
- case type
375
- when :LIMITS_CHANGE
376
- item = [data[0], data[1], data[2]]
377
- if data[4] != :GREEN and data[4] != :GREEN_HIGH and data[4] != :GREEN_LOW and data[4] != :BLUE and data[4] != nil
378
- case data[4]
379
- when :YELLOW, :YELLOW_HIGH, :YELLOW_LOW
380
- to_print = Time.now.formatted << ' ' << "WARN: #{data[0]} #{data[1]} #{data[2]} = #{tlm(data[0], data[1], data[2])} is #{data[4]}\n"
381
- update_log(to_print, 1)
382
- when :RED, :RED_HIGH, :RED_LOW
383
- to_print = Time.now.formatted << ' ' << "ERROR: #{data[0]} #{data[1]} #{data[2]} = #{tlm(data[0], data[1], data[2])} is #{data[4]}\n"
384
- update_log(to_print, 2)
385
- end
386
-
387
- # Limits changed to non-green
388
- unless @out_of_limits_items.includes_item?(item) or @ignored_items.includes_item?(item)
389
- @out_of_limits_items << item
390
- @new_items << item
391
- handle_new_items()
392
- end
393
- elsif data[4] == :GREEN or data[4] == :GREEN_HIGH or data[4] == :GREEN_LOW or data[4] == :BLUE
394
- to_print = Time.now.formatted << ' ' << "INFO: #{data[0]} #{data[1]} #{data[2]} = #{tlm(data[0], data[1], data[2])} returned to GREEN\n"
395
-
396
- if data[4] == :BLUE
397
- update_log(to_print, 3)
398
- else
399
- update_log(to_print, 0)
400
- end
401
- end
402
-
403
- when :LIMITS_SET
404
- if @limits_set != data
405
- break if @cancel_thread
406
- Qt.execute_in_main_thread(true) do
407
- statusBar.showMessage('Limits Set Changed - Reseting')
408
- @initialized = false
409
- to_print = Time.now.formatted << ' ' << "INFO: Limits Set Changed to: #{data}\n"
410
- update_log(to_print, 4)
411
- end
412
- break if @cancel_thread
413
- end
414
-
415
- when :LIMITS_SETTINGS
416
- begin
417
- System.limits.set(data[0], data[1], data[2], data[6], data[7], data[8], data[9], data[10], data[11], data[3], data[4], data[5])
418
- break if @cancel_thread
419
- Qt.execute_in_main_thread(true) do
420
- statusBar.showMessage('Limits Settings Changed - Reseting')
421
- @initialized = false
422
- to_print = Time.now.formatted << ' ' << "INFO: Limits Settings Changed: #{data}\n"
423
- update_log(to_print, 4)
424
- end
425
- break if @cancel_thread
426
- rescue
427
- # This can fail if we missed setting the DEFAULT limits set earlier - Oh well
428
- end
429
- end
430
-
431
- rescue DRb::DRbConnError
432
- break if @cancel_thread
433
- @queue_id = nil
434
- break if @cancel_thread
435
- Qt.execute_in_main_thread(true) do
436
- statusBar.showMessage('Error Connecting to Command and Telemetry Server - Reseting')
437
- @initialized = false
438
- end
439
- break if @cancel_thread
440
- break if @limits_sleeper.sleep(1)
441
- end
442
- end # loop
443
- rescue Exception => error
444
- Cosmos.handle_fatal_exception(error)
445
- end
573
+ # Opens the configuration file and loads the ignored items
574
+ def open_config_file
575
+ filename = Qt::FileDialog::getOpenFileName(self,
576
+ "Open Configuration File", config_path())
577
+ unless filename.nil? || filename.empty?
578
+ result = @limits_items.open_config(filename)
579
+ statusBar.showMessage(tr(result))
446
580
  end
447
581
  end
448
582
 
449
- # Sets up the thread to monitor the limits values and update them when they
450
- # change, as well as updating the status bar at the top of the front panel.
451
- def value_thread
452
- @value_thread = Thread.new do
453
- begin
454
- while true
455
- break if @cancel_thread
456
- unless @items.empty?
457
- Qt.execute_in_main_thread(true) do
458
- begin
459
- # Gather items for widgets
460
- values, limits_states, limits_settings, limits_set = get_tlm_values(@items, :WITH_UNITS)
461
- index = 0
462
- @items.each do |target_name, packet_name, item_name|
463
- begin
464
- System.limits.set(target_name, packet_name, item_name, limits_settings[index][0], limits_settings[index][1], limits_settings[index][2], limits_settings[index][3], limits_settings[index][4], limits_settings[index][5], limits_set) if limits_settings[index]
465
- rescue
466
- # This can fail if we missed setting the DEFAULT limits set earlier - Oh well
467
- end
468
- index += 1
469
- end
470
-
471
- # Handle change in limits set
472
- if limits_set != @value_limits_set
473
- @value_limits_set = limits_set
474
- @widgets.each do |widget|
475
- widget.limits_set = @value_limits_set
476
- end
477
- end
478
-
479
- # Update widgets with values and limits_states
480
- @overall_limits_state = :STALE
481
- (0..(values.length - 1)).each do |widget_index|
482
- limits_state = limits_states[widget_index]
483
- @widgets[widget_index].limits_state = limits_state
484
- @widgets[widget_index].value = values[widget_index]
485
- end
486
-
487
- # Update overall limits state
488
- modify_overall_limits_state(get_overall_limits_state(@ignored_items))
489
- update_overall_limits_state()
490
- rescue DRb::DRbConnError
491
- # Do nothing
492
- end
493
- end
494
- else
495
- @overall_limits_state = :STALE
496
- break if @cancel_thread
497
- Qt.execute_in_main_thread(true) do
498
- begin
499
- modify_overall_limits_state(get_overall_limits_state(@ignored_items))
500
- rescue DRb::DRbConnError
501
- # Do nothing
502
- end
503
- update_overall_limits_state()
504
- end
505
- end
506
-
507
- # Sleep until next polling period
508
- break if @value_sleeper.sleep(1)
509
- end
510
- rescue Exception => error
511
- Cosmos.handle_fatal_exception(error)
512
- end
583
+ # Saves the ignored items to the configuration file
584
+ def save_config_file
585
+ filename = Qt::FileDialog.getSaveFileName(self,
586
+ 'Save As...', config_path(), 'Configuration Files (*.txt)')
587
+ unless filename.nil? || filename.empty?
588
+ result = @limits_items.save_config(filename)
589
+ statusBar.showMessage(tr(result))
590
+ @filename = filename
513
591
  end
514
592
  end
515
593
 
516
- # Checks for the current worst limits state.
517
- #
518
- # @param limits_state [Symbol] State of the current limit being checked.
519
- def modify_overall_limits_state(limits_state)
520
- case @overall_limits_state
521
- when :STALE
522
- @overall_limits_state = limits_state
523
- when :BLUE
524
- if limits_state != nil and limits_state != :STALE
525
- @overall_limits_state = limits_state
526
- end
527
- when :GREEN, :GREEN_HIGH, :GREEN_LOW
528
- if limits_state != nil and limits_state != :STALE and limits_state != :BLUE
529
- @overall_limits_state = limits_state
530
- end
531
- when :YELLOW, :YELLOW_HIGH, :YELLOW_LOW
532
- if limits_state == :RED or limits_state == :RED_HIGH or limits_state == :RED_LOW
533
- @overall_limits_state = limits_state
534
- end
535
- end
536
- end
537
-
538
- # Changes the limits state on the status bar at the top of the screen.
539
- def update_overall_limits_state
540
- text = ''
541
- case @overall_limits_state
542
- when :STALE
543
- palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,0,255))
544
- @monitored_state_text_field.setPalette(palette)
545
- text = 'Stale'
546
- when :GREEN, :GREEN_HIGH, :GREEN_LOW
547
- palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(0,255,0))
548
- @monitored_state_text_field.setPalette(palette)
549
- text = 'Green'
550
- when :YELLOW, :YELLOW_HIGH, :YELLOW_LOW
551
- palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,255,0))
552
- @monitored_state_text_field.setPalette(palette)
553
- text = 'Yellow'
554
- when :RED, :RED_HIGH, :RED_LOW
555
- palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,0,0))
556
- @monitored_state_text_field.setPalette(palette)
557
- text = 'Red'
558
- when :BLUE
559
- palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(0,0,255))
560
- @monitored_state_text_field.setPalette(palette)
561
- text = 'Blue'
562
- end
563
- text << ' - Some Items Ignored' unless @ignored_items.empty?
564
- @monitored_state_text_field.text = text
565
- end
566
-
567
- # Slot to handle when the clear ignored items menu option is selected.
568
- def handle_clear_ignored_items
569
- @ignored_items.clear
570
- @initialized = false
571
- statusBar.showMessage('')
572
- end
573
-
574
- # Slot to handle when the view ignored items menu option is selected.
575
- def handle_view_ignored_items
576
- # Turn Command into scripting text string
577
- string = ''
578
- @ignored_items.each do |item|
579
- string << "#{item[0]} #{item[1]} #{item[2]}\n"
594
+ # Opens a dialog to allow the user to remove ignored items
595
+ def edit_ignored_items
596
+ items = []
597
+ index = 0
598
+ @limits_items.ignored.each do |target_name, packet_name, item_name|
599
+ item = Qt::ListWidgetItem.new("#{target_name} #{packet_name} #{item_name}")
600
+ item.setData(Qt::UserRole, Qt::Variant.new(@limits_items.ignored[index]))
601
+ items << item
602
+ index += 1
580
603
  end
581
604
 
582
- # Show Dialog box with text displaying ignored items
583
605
  Qt::Dialog.new(self) do |dialog|
584
606
  dialog.setWindowTitle('Ignored Telemetry Items')
585
- text = Qt::PlainTextEdit.new
586
- text.setReadOnly(true)
587
- text.setPlainText(string) if string
607
+ list = Qt::ListWidget.new
608
+ list.setFocus()
609
+ # Allow multiple sections
610
+ list.setSelectionMode(Qt::AbstractItemView::ExtendedSelection)
611
+ items.each {|item| list.addItem(item) }
612
+
613
+ shortcut = Qt::Shortcut.new(Qt::KeySequence.new(Qt::KeySequence::Delete), list)
614
+ list.connect(shortcut, SIGNAL('activated()')) do
615
+ items = list.selectedItems()
616
+ (0...items.length).each do |index|
617
+ @limits_items.remove_ignored(items[index].data(Qt::UserRole).value)
618
+ end
619
+ list.remove_selected_items
620
+ list.setCurrentRow(0)
621
+ end
622
+ # Preselect the first row (works if list is empty) so the keyboard
623
+ # works instantly without having to click the list
624
+ list.setCurrentRow(0)
588
625
 
589
626
  ok = Qt::PushButton.new('Ok') do
590
627
  connect(SIGNAL('clicked()')) { dialog.done(0) }
591
628
  end
592
-
593
- dialog.layout = Qt::VBoxLayout.new do
594
- addWidget(text)
629
+ remove = Qt::PushButton.new('Remove Selected') do
630
+ connect(SIGNAL('clicked()')) { shortcut.activated() }
631
+ end
632
+ button_layout = Qt::HBoxLayout.new do
595
633
  addWidget(ok)
634
+ addStretch(1)
635
+ addWidget(remove)
636
+ end
637
+ dialog.layout = Qt::VBoxLayout.new do
638
+ addWidget(list)
639
+ addLayout(button_layout)
596
640
  end
597
-
598
641
  dialog.resize(500, 200)
599
642
  dialog.exec
600
643
  dialog.dispose
601
644
  end
602
645
  end
603
646
 
604
- # Slot to handle when the save ignored items menu option is selected.
605
- def handle_save_ignored_items
606
- if @ignored_filename
607
- filename = Qt::FileDialog.getSaveFileName(self, 'Save As...', @ignored_filename, 'Configuration Files (*.txt)')
608
- else
609
- filename = Qt::FileDialog.getSaveFileName(self, 'Save As...', File.join(::Cosmos::USERPATH, 'config', 'tools', 'limits_monitor', 'limits_monitor.txt'), 'Configuration Files (*.txt)')
610
- end
611
- unless filename.nil? or filename.empty?
612
- begin
613
- File.open(filename, "w") do |file|
614
- @ignored_items.each do |target, pkt_name, item_name|
615
- file.puts("IGNORE #{target} #{pkt_name} #{item_name}")
616
- end
647
+ # Thread to monitor for broken limits and add them to the log and
648
+ # front panel when found.
649
+ def limits_thread
650
+ result = nil
651
+ color = nil
652
+ @limits_thread = Thread.new do
653
+ while true
654
+ break if @cancel_thread
655
+ Qt.execute_in_main_thread(true) do
656
+ result, color = @limits_items.process_events()
657
+ end
658
+ if result
659
+ update_log(result, color)
660
+ else
661
+ break if @limits_sleeper.sleep(1)
617
662
  end
618
- statusBar.showMessage("#{filename} saved")
619
- rescue => e
620
- statusBar.showMessage("Error Saving Configuration : #{e.message}")
621
663
  end
622
664
  end
665
+ rescue Exception => error
666
+ Cosmos.handle_fatal_exception(error)
623
667
  end
624
668
 
625
- # Slot to handle when the load ignored items menu option is selected.
626
- def handle_open_ignored_items
627
- if @ignored_filename
628
- filename = Qt::FileDialog::getOpenFileName(self, "Open Configuration File", @ignored_filename)
629
- else
630
- filename = Qt::FileDialog::getOpenFileName(self, "Open Configuration File", File.join(::Cosmos::USERPATH, 'config', 'tools', 'limits_monitor', 'limits_monitor.txt'))
631
- end
632
- unless filename.nil? or filename.empty?
633
- process_config(filename)
669
+ # Add new out of limit item or stale packet
670
+ #
671
+ # @param target_name [String] Target name of out of limits item.
672
+ # @param packet_name [String] Packet name of out of limits item.
673
+ # @param item_name [String] Item name of out of limits item or nil
674
+ # if its a stale packet
675
+ # @return [Qt::Widget] The new widget that was created
676
+ def new_gui_item(target_name, packet_name, item_name)
677
+ widget = nil
678
+ Qt.execute_in_main_thread(true) do
679
+ widget = LimitsWidget.new(self, target_name, packet_name, item_name)
680
+ @scroll_layout.addWidget(widget)
634
681
  end
682
+ widget
635
683
  end
636
684
 
637
- # Handle config file if present.
685
+ # Update out of limit item with a values
638
686
  #
639
- # @param filename [String] Filename for the config file if present.
640
- def process_config(filename)
641
- @initialized = false
642
- @ignored_items = []
643
-
644
- begin
645
- parser = ConfigParser.new
646
- parser.parse_file(filename) do |keyword, params|
647
- case keyword
648
- when 'IGNORE'
649
- ignore_item([params[0], params[1], params[2]])
650
- end
651
- end
652
- statusBar.showMessage("#{filename} loaded")
653
- rescue => e
654
- statusBar.showMessage("Error Loading Configuration : #{e.message}")
687
+ # @param target_name [String] Target name of out of limits item.
688
+ # @param packet_name [String] Packet name of out of limits item.
689
+ # @param item_name [String] Item name of out of limits item or nil
690
+ # if its a stale packet
691
+ def update_gui_item(widget, value, limits_state, limits_set)
692
+ Qt.execute_in_main_thread(true) do
693
+ widget.set_values(value, limits_state, limits_set) if widget
655
694
  end
656
695
  end
657
696
 
697
+ # Reset the GUI by clearing all items
698
+ def clear_gui_items
699
+ Qt.execute_in_main_thread(true) { @scroll_layout.removeAll }
700
+ end
701
+
658
702
  # Update front panel to ignore an item when the corresponding button is pressed.
659
703
  #
660
- # @param item [Array] Array containing the target, packet, and item name of the
661
- # item to ignore.
662
- def ignore_item(item)
704
+ # @param item [Array<String,String,String] Array containing the target name,
705
+ # packet name, and item name of the item to ignore.
706
+ def ignore(widget, item)
707
+ @limits_items.ignore(item)
663
708
  Qt.execute_in_main_thread(true) do
664
- @ignored_items << item
665
- delete_index = @items.delete_item(item)
666
- if delete_index
667
- hframe_to_dispose = @widget_hframes[delete_index]
668
- widget_to_dispose = @widgets[delete_index]
669
- button_to_dispose = @buttons[delete_index]
670
- @widgets.delete_at(delete_index)
671
- @buttons.delete_at(delete_index)
672
- @widget_hframes.delete_at(delete_index)
673
- @scroll_layout.removeItem(hframe_to_dispose)
674
- widget_to_dispose.dispose
675
- button_to_dispose.dispose
676
- hframe_to_dispose.dispose
677
- @scroll.repaint
678
- end
709
+ @scroll_layout.removeWidget(widget)
710
+ widget.dispose
711
+ @scroll_widget.adjustSize
679
712
  statusBar.showMessage('Warning: Some Telemetry Items are Ignored')
680
713
  end
681
714
  end
682
715
 
683
- # Process any new items that become out of limits.
684
- def handle_new_items
685
- @new_widgets = []
686
-
716
+ # Update the log panel with limits change information.
717
+ #
718
+ # @param message [String] Text string with information about which item is out of
719
+ # limits, what its value is, and what limit was broken (red_low, yellow_low, etc.)
720
+ # @param color [Symbol] Color of text to add.
721
+ def update_log(message, color)
722
+ return if @cancel_thread
687
723
  Qt.execute_in_main_thread(true) do
688
- @new_items.each do |target, pkt_name, item_name|
689
- # Create widgets for new items
690
- add_items(target, pkt_name, item_name)
724
+ @tf ||= Qt::TextCharFormat.new
725
+ case color
726
+ when :GREEN
727
+ brush = Cosmos.getBrush(Cosmos::GREEN)
728
+ when :YELLOW
729
+ brush = Cosmos.getBrush(Cosmos::YELLOW)
730
+ when :RED
731
+ brush = Cosmos.getBrush(Cosmos::RED)
732
+ when :BLUE
733
+ brush = Cosmos.getBrush(Cosmos::BLUE)
734
+ else # :BLACK
735
+ brush = Cosmos.getBrush(Cosmos::BLACK)
691
736
  end
737
+ @tf.setForeground(brush)
738
+ @log_output.setCurrentCharFormat(@tf)
739
+ @log_output.appendPlainText(message.chomp)
692
740
  end
741
+ end
693
742
 
694
- # Set initial values
695
-
696
- Qt.execute_in_main_thread(true) do
697
- begin
698
- values, limits_states, limits_settings, limits_set = get_tlm_values(@new_items, :WITH_UNITS)
699
- index = 0
700
- @new_items.each do |target_name, packet_name, item_name|
701
- begin
702
- System.limits.set(target_name, packet_name, item_name, limits_settings[index][0], limits_settings[index][1], limits_settings[index][2], limits_settings[index][3], limits_settings[index][4], limits_settings[index][5], limits_set) if limits_settings[index]
703
- rescue
704
- # This can fail if we missed setting the DEFAULT limits set earlier - Oh well
743
+ # Thread to request the out of limits values and update them at 1Hz.
744
+ # Also updates the status bar at the top of the front panel indicating
745
+ # the overall limits value of the system.
746
+ def value_thread
747
+ @value_thread = Thread.new do
748
+ while true
749
+ break if @cancel_thread
750
+ Qt.execute_in_main_thread(true) do
751
+ if @limits_items.initialized
752
+ @limits_items.update_values()
753
+ update_overall_limits_state(@limits_items.overall_state())
754
+ else
755
+ # Set the status bar message to expire in 2s since this runs at 1Hz
756
+ statusBar.showMessage('Error Connecting to Command and Telemetry Server', 2000)
705
757
  end
706
- index += 1
707
- end
708
- index = 0
709
-
710
- @new_widgets.each do |widget|
711
- limits_state = limits_states[index]
712
- widget.limits_set = limits_set
713
- widget.limits_state = limits_state
714
- widget.value = values[index]
715
- index += 1
716
- modify_overall_limits_state(limits_state)
717
- end
718
-
719
- update_overall_limits_state()
720
-
721
- @new_items.each do |item|
722
- @items << item
723
758
  end
724
- @new_items = []
725
- rescue DRb::DRbConnError
726
- statusBar.showMessage('Error Connecting to Command and Telemetry Server - Reseting')
727
- @initialized = false
759
+ break if @value_sleeper.sleep(1)
728
760
  end
729
761
  end
762
+ rescue Exception => error
763
+ Cosmos.handle_fatal_exception(error)
730
764
  end
731
765
 
732
- # Reset front panel and log when the reset menu option is selected.
733
- def handle_reset
766
+ # Changes the limits state on the status bar at the top of the screen.
767
+ def update_overall_limits_state(state)
734
768
  Qt.execute_in_main_thread(true) do
735
- @widgets.each {|widget| widget.dispose}
736
- @buttons.each {|button| button.dispose}
737
- @widget_hframes.each do |hframe|
738
- @scroll_layout.removeItem(hframe)
739
- hframe.dispose
769
+ text = ''
770
+ case state
771
+ when :STALE
772
+ palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,0,255))
773
+ @monitored_state_text_field.setPalette(palette)
774
+ text = 'Stale'
775
+ when :GREEN, :GREEN_HIGH, :GREEN_LOW
776
+ palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(0,255,0))
777
+ @monitored_state_text_field.setPalette(palette)
778
+ text = 'Green'
779
+ when :YELLOW, :YELLOW_HIGH, :YELLOW_LOW
780
+ palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,255,0))
781
+ @monitored_state_text_field.setPalette(palette)
782
+ text = 'Yellow'
783
+ when :RED, :RED_HIGH, :RED_LOW
784
+ palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,0,0))
785
+ @monitored_state_text_field.setPalette(palette)
786
+ text = 'Red'
787
+ when :BLUE
788
+ palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(0,0,255))
789
+ @monitored_state_text_field.setPalette(palette)
790
+ text = 'Blue'
740
791
  end
741
- @scroll.repaint
742
-
743
- @widgets = []
744
- @new_widgets = []
745
- @buttons = []
746
- @widget_hframes = []
747
- @items = []
748
- @value_limits_set = :DEFAULT
749
-
750
- @overall_limits_state = :STALE
751
- update_overall_limits_state()
792
+ text << ' - Some Items Ignored' if @limits_items.ignored_items?
793
+ @monitored_state_text_field.text = text
752
794
  end
753
795
  end
754
796
 
755
- # Handle the window closing.
797
+ # Handle the window closing
756
798
  def closeEvent(event)
757
799
  @cancel_thread = true
758
800
  @value_sleeper.cancel
@@ -792,5 +834,4 @@ module Cosmos
792
834
  end
793
835
 
794
836
  end # class LimitsMonitor
795
-
796
837
  end # module Cosmos