cosmos 3.4.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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