cosmos 3.1.2 → 3.2.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/Manifest.txt +17 -1
  4. data/autohotkey/tools/test_runner2.ahk +1 -0
  5. data/autohotkey/tools/tlm_grapher.ahk +13 -1
  6. data/data/crc.txt +39 -30
  7. data/demo/config/data/crc.txt +3 -3
  8. data/demo/config/targets/TEMPLATED/lib/templated_interface.rb +3 -1
  9. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +7 -1
  10. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +6 -1
  11. data/lib/cosmos.rb +2 -2
  12. data/lib/cosmos/gui/dialogs/about_dialog.rb +18 -5
  13. data/lib/cosmos/gui/dialogs/tlm_details_dialog.rb +0 -7
  14. data/lib/cosmos/gui/line_graph/overview_graph.rb +12 -2
  15. data/lib/cosmos/gui/utilities/script_module_gui.rb +11 -3
  16. data/lib/cosmos/interfaces/interface.rb +12 -0
  17. data/lib/cosmos/interfaces/stream_interface.rb +1 -21
  18. data/lib/cosmos/interfaces/tcpip_server_interface.rb +10 -0
  19. data/lib/cosmos/io/json_drb_object.rb +75 -56
  20. data/lib/cosmos/io/tcpip_server.rb +1 -11
  21. data/lib/cosmos/packet_logs.rb +1 -0
  22. data/lib/cosmos/packet_logs/ccsds_log_reader.rb +103 -0
  23. data/lib/cosmos/packets/packet.rb +70 -1
  24. data/lib/cosmos/packets/packet_config.rb +59 -611
  25. data/lib/cosmos/packets/parsers/format_string_parser.rb +58 -0
  26. data/lib/cosmos/packets/parsers/limits_parser.rb +146 -0
  27. data/lib/cosmos/packets/parsers/limits_response_parser.rb +52 -0
  28. data/lib/cosmos/packets/parsers/macro_parser.rb +116 -0
  29. data/lib/cosmos/packets/parsers/packet_item_parser.rb +215 -0
  30. data/lib/cosmos/packets/parsers/packet_parser.rb +123 -0
  31. data/lib/cosmos/packets/parsers/processor_parser.rb +63 -0
  32. data/lib/cosmos/packets/parsers/state_parser.rb +116 -0
  33. data/lib/cosmos/packets/structure.rb +59 -22
  34. data/lib/cosmos/packets/structure_item.rb +1 -1
  35. data/lib/cosmos/script/script.rb +4 -5
  36. data/lib/cosmos/streams/serial_stream.rb +5 -0
  37. data/lib/cosmos/streams/stream.rb +8 -2
  38. data/lib/cosmos/streams/stream_protocol.rb +1 -0
  39. data/lib/cosmos/streams/tcpip_client_stream.rb +37 -7
  40. data/lib/cosmos/streams/tcpip_socket_stream.rb +9 -6
  41. data/lib/cosmos/system/target.rb +3 -6
  42. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +57 -48
  43. data/lib/cosmos/tools/cmd_tlm_server/interface_thread.rb +7 -3
  44. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +1 -1
  45. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +7 -1
  46. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +1 -2
  47. data/lib/cosmos/top_level.rb +22 -11
  48. data/lib/cosmos/utilities/message_log.rb +14 -9
  49. data/lib/cosmos/version.rb +5 -5
  50. data/spec/interfaces/cmd_tlm_server_interface_spec.rb +16 -16
  51. data/spec/interfaces/linc_interface_spec.rb +3 -0
  52. data/spec/interfaces/tcpip_client_interface_spec.rb +1 -0
  53. data/spec/interfaces/tcpip_server_interface_spec.rb +9 -0
  54. data/spec/io/json_drb_object_spec.rb +1 -1
  55. data/spec/io/serial_driver_spec.rb +0 -1
  56. data/spec/packet_logs/packet_log_writer_spec.rb +5 -3
  57. data/spec/packets/packet_config_spec.rb +22 -837
  58. data/spec/packets/packet_item_spec.rb +10 -10
  59. data/spec/packets/packet_spec.rb +239 -1
  60. data/spec/packets/parsers/format_string_parser_spec.rb +122 -0
  61. data/spec/packets/parsers/limits_parser_spec.rb +282 -0
  62. data/spec/packets/parsers/limits_response_parser_spec.rb +149 -0
  63. data/spec/packets/parsers/macro_parser_spec.rb +184 -0
  64. data/spec/packets/parsers/packet_item_parser_spec.rb +306 -0
  65. data/spec/packets/parsers/packet_parser_spec.rb +99 -0
  66. data/spec/packets/parsers/processor_parser_spec.rb +114 -0
  67. data/spec/packets/parsers/state_parser_spec.rb +156 -0
  68. data/spec/packets/structure_item_spec.rb +14 -14
  69. data/spec/packets/structure_spec.rb +162 -16
  70. data/spec/streams/fixed_stream_protocol_spec.rb +7 -4
  71. data/spec/streams/length_stream_protocol_spec.rb +3 -0
  72. data/spec/streams/preidentified_stream_protocol_spec.rb +3 -0
  73. data/spec/streams/serial_stream_spec.rb +12 -0
  74. data/spec/streams/stream_protocol_spec.rb +14 -0
  75. data/spec/streams/stream_spec.rb +1 -0
  76. data/spec/streams/tcpip_client_stream_spec.rb +3 -0
  77. data/spec/streams/tcpip_socket_stream_spec.rb +15 -3
  78. data/spec/streams/template_stream_protocol_spec.rb +5 -0
  79. data/spec/streams/terminated_stream_protocol_spec.rb +4 -0
  80. data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +21 -1
  81. data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +1 -1
  82. data/spec/tools/cmd_tlm_server/interfaces_spec.rb +1 -1
  83. metadata +19 -3
@@ -123,6 +123,11 @@ module Cosmos
123
123
  end
124
124
  end
125
125
 
126
+ # Connect the stream
127
+ def connect
128
+ # N/A - Serial streams 'connect' on creation
129
+ end
130
+
126
131
  # @return [Boolean] Whether the serial stream is connected to the serial
127
132
  # port
128
133
  def connected?
@@ -12,8 +12,8 @@ require 'timeout' # For Timeout::Error
12
12
 
13
13
  module Cosmos
14
14
 
15
- # Interface that implments the following methods: read, write(data),
16
- # connected? and disconnect. Streams are simply data sources which
15
+ # Class that implments the following methods: read, write(data),
16
+ # connect, connected? and disconnect. Streams are simply data sources which
17
17
  # {StreamProtocol} classes read and write to. This separation of concerns
18
18
  # allows Streams to simply focus on getting and sending raw data while the
19
19
  # higher level processing occurs in {StreamProtocol}.
@@ -42,12 +42,18 @@ module Cosmos
42
42
  raise "write not defined by Stream"
43
43
  end
44
44
 
45
+ # Connects the stream
46
+ def connect
47
+ raise "connect not defined by Stream"
48
+ end
49
+
45
50
  # @return [Boolean] true if connected or false otherwise
46
51
  def connected?
47
52
  raise "connected? not defined by Stream"
48
53
  end
49
54
 
50
55
  # Disconnects the stream
56
+ # Note that streams are not designed to be reconnected and must be recreated
51
57
  def disconnect
52
58
  raise "disconnect not defined by Stream"
53
59
  end
@@ -103,6 +103,7 @@ module Cosmos
103
103
  @data = ''
104
104
  @data.force_encoding('ASCII-8BIT')
105
105
  @stream = stream
106
+ @stream.connect
106
107
  end
107
108
 
108
109
  # @return [Boolean] Whether the stream attribute has been set and is
@@ -28,7 +28,7 @@ module Cosmos
28
28
  # a write only stream.
29
29
  # @param write_timeout (see TcpipSocketStream#initialize)
30
30
  # @param read_timeout (see TcpipSocketStream#initialize)
31
- def initialize(hostname, write_port, read_port, write_timeout, read_timeout)
31
+ def initialize(hostname, write_port, read_port, write_timeout, read_timeout, connect_timeout = 5.0)
32
32
  @hostname = hostname
33
33
  if (@hostname.to_s.upcase == 'LOCALHOST')
34
34
  @hostname = '127.0.0.1'
@@ -41,8 +41,8 @@ module Cosmos
41
41
  write_addr = nil
42
42
  read_addr = nil
43
43
  begin
44
- write_addr = Socket.pack_sockaddr_in(@write_port, @hostname) if @write_port
45
- read_addr = Socket.pack_sockaddr_in(@read_port, @hostname) if @read_port
44
+ @write_addr = Socket.pack_sockaddr_in(@write_port, @hostname) if @write_port
45
+ @read_addr = Socket.pack_sockaddr_in(@read_port, @hostname) if @read_port
46
46
  rescue => error
47
47
  if error.message =~ /getaddrinfo/
48
48
  raise "Invalid hostname: #{@hostname}"
@@ -52,26 +52,56 @@ module Cosmos
52
52
  end
53
53
 
54
54
  write_socket = nil
55
- if write_addr
55
+ if @write_addr
56
56
  write_socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
57
57
  write_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
58
- write_socket.connect(write_addr)
59
58
  end
60
59
 
61
60
  read_socket = nil
62
- if read_addr
61
+ if @read_addr
63
62
  if @write_port != @read_port
64
63
  read_socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
65
64
  read_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
66
- read_socket.connect(read_addr)
67
65
  else
68
66
  read_socket = write_socket
69
67
  end
70
68
  end
71
69
 
70
+ @connect_timeout = ConfigParser.handle_nil(connect_timeout)
71
+ @connect_timeout = @connect_timeout.to_f if @connect_timeout
72
+
72
73
  super(write_socket, read_socket, write_timeout, read_timeout)
73
74
  end
74
75
 
76
+ # Connect the socket(s)
77
+ def connect
78
+ connect_nonblock(@write_socket, @write_addr) if @write_socket
79
+ connect_nonblock(@read_socket, @read_addr) if @read_socket and @read_socket != @write_socket
80
+ super()
81
+ end
82
+
83
+ protected
84
+
85
+ def connect_nonblock(socket, addr)
86
+ begin
87
+ socket.connect_nonblock(addr)
88
+ rescue IO::WaitWritable
89
+ begin
90
+ _, sockets, _ = IO.select(nil, [socket], nil, @connect_timeout) # wait 3-way handshake completion
91
+ rescue Errno::ENOTSOCK
92
+ raise "Connect canceled"
93
+ end
94
+ if sockets and !sockets.empty?
95
+ begin
96
+ socket.connect_nonblock(addr) # check connection failure
97
+ rescue Errno::EISCONN
98
+ end
99
+ else
100
+ raise "Connect timeout"
101
+ end
102
+ end
103
+ end
104
+
75
105
  end # class TcpipClientStream
76
106
 
77
107
  end # module Cosmos
@@ -35,7 +35,6 @@ module Cosmos
35
35
  @write_timeout = @write_timeout.to_f if @write_timeout
36
36
  @read_timeout = ConfigParser.handle_nil(read_timeout)
37
37
  @read_timeout = @read_timeout.to_f if @read_timeout
38
- @connected = true
39
38
 
40
39
  # Mutex on write is needed to protect from commands coming in from more
41
40
  # than one tool
@@ -120,6 +119,12 @@ module Cosmos
120
119
  end
121
120
  end
122
121
 
122
+ # Connect the stream
123
+ def connect
124
+ # If called directly this class is acting as a server and does not need to connect the sockets
125
+ @connected = true
126
+ end
127
+
123
128
  # @return [Boolean] Whether the sockets are connected
124
129
  def connected?
125
130
  @connected
@@ -127,11 +132,9 @@ module Cosmos
127
132
 
128
133
  # Disconnect by closing the sockets
129
134
  def disconnect
130
- if @connected
131
- @write_socket.close if @write_socket and !@write_socket.closed?
132
- @read_socket.close if @read_socket and !@read_socket.closed?
133
- @connected = false
134
- end
135
+ @write_socket.close if @write_socket and !@write_socket.closed?
136
+ @read_socket.close if @read_socket and !@read_socket.closed?
137
+ @connected = false
135
138
  end
136
139
 
137
140
  end # class TcpipSocketStream
@@ -135,12 +135,9 @@ module Cosmos
135
135
  usage = "#{keyword} <FILENAME>"
136
136
  parser.verify_num_parameters(1, 1, usage)
137
137
  begin
138
- require parameters[0]
139
- rescue LoadError => err
140
- msg = "Unable to require #{parameters[0]} due to #{err.message}. " +
141
- "Ensure #{parameters[0]} is in the COSMOS lib directory."
142
- Logger.instance.error msg
143
- raise parser.error(msg)
138
+ Cosmos.require_file(parameters[0])
139
+ rescue Exception => err
140
+ raise parser.error(err.message)
144
141
  end
145
142
  @requires << parameters[0]
146
143
 
@@ -61,9 +61,9 @@ module Cosmos
61
61
  # @param recursive [Boolean] Whether process_file is being called
62
62
  # recursively
63
63
  def process_file(filename, recursive = false)
64
- current_interface = nil
64
+ current_interface_or_router = nil
65
+ current_type = nil
65
66
  current_interface_log_added = false
66
- current_router = nil
67
67
 
68
68
  Logger.info "Processing CmdTlmServer configuration in file: #{File.expand_path(filename)}"
69
69
 
@@ -122,94 +122,103 @@ module Cosmos
122
122
  parser.verify_num_parameters(2, nil, usage)
123
123
  interface_class = Cosmos.require_class(params[1])
124
124
  if params[2]
125
- current_interface = interface_class.new(*params[2..-1])
125
+ current_interface_or_router = interface_class.new(*params[2..-1])
126
126
  else
127
- current_interface = interface_class.new
127
+ current_interface_or_router = interface_class.new
128
128
  end
129
+ current_type = :INTERFACE
129
130
  current_interface_log_added = false
130
- current_interface.packet_log_writer_pairs << @packet_log_writer_pairs['DEFAULT']
131
- current_interface.name = params[0].upcase
132
- @interfaces[params[0].upcase] = current_interface
131
+ current_interface_or_router.packet_log_writer_pairs << @packet_log_writer_pairs['DEFAULT']
132
+ current_interface_or_router.name = params[0].upcase
133
+ @interfaces[params[0].upcase] = current_interface_or_router
133
134
 
134
- when 'DONT_CONNECT', 'DONT_RECONNECT', 'RECONNECT_DELAY', 'DISABLE_DISCONNECT', 'LOG', 'DONT_LOG', 'TARGET', 'LOG_RAW'
135
- raise parser.error("No current interface for #{keyword}") unless current_interface
135
+ when 'LOG', 'DONT_LOG', 'TARGET'
136
+ raise parser.error("No current interface for #{keyword}") unless current_interface_or_router and current_type == :INTERFACE
136
137
 
137
138
  case keyword
138
139
 
139
- when 'DONT_CONNECT'
140
- parser.verify_num_parameters(0, 0, "#{keyword}")
141
- current_interface.connect_on_startup = false
142
-
143
- when 'DONT_RECONNECT'
144
- parser.verify_num_parameters(0, 0, "#{keyword}")
145
- current_interface.auto_reconnect = false
146
-
147
- when 'RECONNECT_DELAY'
148
- parser.verify_num_parameters(1, 1, "#{keyword} <Delay in Seconds>")
149
- current_interface.reconnect_delay = Float(params[0])
150
-
151
- when 'DISABLE_DISCONNECT'
152
- parser.verify_num_parameters(0, 0, "#{keyword}")
153
- current_interface.disable_disconnect = true
154
-
155
140
  when 'LOG'
156
141
  parser.verify_num_parameters(1, 1, "#{keyword} <Packet Log Writer Name>")
157
142
  packet_log_writer_pair = @packet_log_writer_pairs[params[0].upcase]
158
143
  raise parser.error("Unknown packet log writer: #{params[0].upcase}") unless packet_log_writer_pair
159
- current_interface.packet_log_writer_pairs.delete(@packet_log_writer_pairs['DEFAULT']) unless current_interface_log_added
144
+ current_interface_or_router.packet_log_writer_pairs.delete(@packet_log_writer_pairs['DEFAULT']) unless current_interface_log_added
160
145
  current_interface_log_added = true
161
- current_interface.packet_log_writer_pairs << packet_log_writer_pair unless current_interface.packet_log_writer_pairs.include?(packet_log_writer_pair)
146
+ current_interface_or_router.packet_log_writer_pairs << packet_log_writer_pair unless current_interface_or_router.packet_log_writer_pairs.include?(packet_log_writer_pair)
162
147
 
163
148
  when 'DONT_LOG'
164
149
  parser.verify_num_parameters(0, 0, "#{keyword}")
165
- current_interface.packet_log_writer_pairs = []
150
+ current_interface_or_router.packet_log_writer_pairs = []
166
151
 
167
152
  when 'TARGET'
168
153
  parser.verify_num_parameters(1, 1, "#{keyword} <Target Name>")
169
154
  target_name = params[0].upcase
170
155
  target = System.targets[target_name]
171
156
  if target
172
- target.interface = current_interface
173
- current_interface.target_names << target_name
157
+ target.interface = current_interface_or_router
158
+ current_interface_or_router.target_names << target_name
174
159
  else
175
- raise parser.error("Unknown target #{target_name} mapped to interface #{current_interface.name}")
160
+ raise parser.error("Unknown target #{target_name} mapped to interface #{current_interface_or_router.name}")
176
161
  end
177
162
 
178
- when 'LOG_RAW'
163
+ end # end case keyword for all keywords that require a current interface
164
+
165
+ when 'DONT_CONNECT', 'DONT_RECONNECT', 'RECONNECT_DELAY', 'DISABLE_DISCONNECT', 'LOG_RAW', 'ROUTER_LOG_RAW', 'OPTION'
166
+ raise parser.error("No current interface or router for #{keyword}") unless current_interface_or_router
167
+
168
+ case keyword
169
+
170
+ when 'DONT_CONNECT'
171
+ parser.verify_num_parameters(0, 0, "#{keyword}")
172
+ current_interface_or_router.connect_on_startup = false
173
+
174
+ when 'DONT_RECONNECT'
175
+ parser.verify_num_parameters(0, 0, "#{keyword}")
176
+ current_interface_or_router.auto_reconnect = false
177
+
178
+ when 'RECONNECT_DELAY'
179
+ parser.verify_num_parameters(1, 1, "#{keyword} <Delay in Seconds>")
180
+ current_interface_or_router.reconnect_delay = Float(params[0])
181
+
182
+ when 'DISABLE_DISCONNECT'
183
+ parser.verify_num_parameters(0, 0, "#{keyword}")
184
+ current_interface_or_router.disable_disconnect = true
185
+
186
+ # TODO: Deprecate ROUTER_LOG_RAW
187
+ when 'LOG_RAW', 'ROUTER_LOG_RAW'
179
188
  parser.verify_num_parameters(0, nil, "#{keyword} <Raw Logger Class File (optional)> <Raw Logger Parameters (optional)>")
180
- current_interface.raw_logger_pair = RawLoggerPair.new(current_interface.name, params)
189
+ current_interface_or_router.raw_logger_pair = RawLoggerPair.new(current_interface_or_router.name, params)
181
190
 
182
- end # end case keyword for all keywords that require a current interface
191
+ when 'OPTION'
192
+ parser.verify_num_parameters(2, nil, "#{keyword} <Option Name> <Option Value 1> <Option Value 2 (optional)> <etc>")
193
+ current_interface_or_router.set_option(params[0], params[1..-1])
194
+
195
+ end # end case keyword for all keywords that require a current interface or router
183
196
 
184
197
  when 'ROUTER'
185
198
  usage = "ROUTER <Name> <Filename> <Specific Parameters>"
186
199
  parser.verify_num_parameters(2, nil, usage)
187
200
  router_class = Cosmos.require_class(params[1])
188
201
  if params[2]
189
- current_router = router_class.new(*params[2..-1])
202
+ current_interface_or_router = router_class.new(*params[2..-1])
190
203
  else
191
- current_router = router_class.new
204
+ current_interface_or_router = router_class.new
192
205
  end
193
- current_router.name = params[0].upcase
194
- @routers[params[0].upcase] = current_router
206
+ current_type = :ROUTER
207
+ current_interface_or_router.name = params[0].upcase
208
+ @routers[params[0].upcase] = current_interface_or_router
195
209
 
196
210
  when 'ROUTE'
197
- raise parser.error("No current router for #{keyword}") unless current_router
211
+ raise parser.error("No current router for #{keyword}") unless current_interface_or_router and current_type == :ROUTER
198
212
  usage = "ROUTE <Interface Name>"
199
213
  parser.verify_num_parameters(1, 1, usage)
200
214
  interface_name = params[0].upcase
201
215
  interface = @interfaces[interface_name]
202
- raise parser.error("Unknown interface #{interface_name} mapped to router #{current_router.name}") unless interface
203
- unless current_router.interfaces.include? interface
204
- current_router.interfaces << interface
205
- interface.routers << current_router
216
+ raise parser.error("Unknown interface #{interface_name} mapped to router #{current_interface_or_router.name}") unless interface
217
+ unless current_interface_or_router.interfaces.include? interface
218
+ current_interface_or_router.interfaces << interface
219
+ interface.routers << current_interface_or_router
206
220
  end
207
221
 
208
- when 'ROUTER_LOG_RAW'
209
- raise parser.error("No current router for #{keyword}") unless current_router
210
- parser.verify_num_parameters(0, nil, "#{keyword} <Raw Logger Class File (optional)> <Raw Logger Parameters (optional)>")
211
- current_router.raw_logger_pair = RawLoggerPair.new(current_router.name, params)
212
-
213
222
  when 'BACKGROUND_TASK'
214
223
  usage = "#{keyword} <Filename> <Specific Parameters>"
215
224
  parser.verify_num_parameters(1, nil, usage)
@@ -183,12 +183,16 @@ module Cosmos
183
183
  if @connection_failed_callback
184
184
  @connection_failed_callback.call(connect_error)
185
185
  else
186
- Logger.error "#{@interface.name} Connection Failed: #{connect_error.to_s}"
186
+ Logger.error "#{@interface.name} Connection Failed: #{connect_error.class}:#{connect_error.message}"
187
187
  case connect_error
188
- when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT
188
+ when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::ENOTSOCK
189
189
  # Do not write an exception file for these extremely common cases
190
190
  else
191
- Cosmos.write_exception_file(connect_error)
191
+ if RuntimeError === connect_error and (connect_error.message =~ /canceled/ or connect_error.message =~ /timeout/)
192
+ # Do not write an exception file for these extremely common cases
193
+ else
194
+ Cosmos.write_exception_file(connect_error)
195
+ end
192
196
  end
193
197
  end
194
198
  disconnect()
@@ -757,7 +757,7 @@ module Cosmos
757
757
  @cancel_thread = true
758
758
  @value_sleeper.cancel
759
759
  @limits_sleeper.cancel
760
- script_disconnect()
760
+ shutdown_cmd_tlm()
761
761
  Cosmos.kill_thread(self, @limits_thread, 2)
762
762
  Cosmos.kill_thread(self, @value_thread, 2)
763
763
  super(event)
@@ -64,7 +64,13 @@ module Cosmos
64
64
  end # def kill
65
65
 
66
66
  def graceful_kill
67
- # Just to remove warning
67
+ # Allow the callbacks a chance to update the GUI so that they can die gracefully
68
+ if defined? Qt and Thread.current == Thread.main
69
+ 5.times do
70
+ Qt::CoreApplication.instance.processEvents
71
+ sleep(0.05)
72
+ end
73
+ end
68
74
  end
69
75
 
70
76
  end # class TabbedPlotsRealtimeThread
@@ -433,9 +433,8 @@ module Cosmos
433
433
  event.ignore()
434
434
  else
435
435
  # Close any open screens
436
- script_disconnect()
437
- Screen.close_all_screens(self)
438
436
  shutdown_cmd_tlm()
437
+ Screen.close_all_screens(self)
439
438
  @json_drb.stop_service if @json_drb
440
439
  super(event)
441
440
  end
@@ -527,13 +527,7 @@ module Cosmos
527
527
  def self.require_class(class_filename)
528
528
  class_name = class_filename.filename_to_class_name
529
529
  return class_name.to_class if class_name.to_class and defined? class_name.to_class
530
- begin
531
- require class_filename
532
- rescue LoadError => err
533
- msg = "Unable to require #{class_filename} due to #{err.message}. Ensure #{class_filename} is in the COSMOS lib directory."
534
- Logger.error msg
535
- raise msg
536
- end
530
+ self.require_file(class_filename)
537
531
  klass = class_name.to_class
538
532
  if klass
539
533
  return klass
@@ -542,6 +536,19 @@ module Cosmos
542
536
  end
543
537
  end
544
538
 
539
+ # Requires a file with a standard error message if it fails
540
+ #
541
+ # @param filename [String] The name of the file to require
542
+ def self.require_file(filename)
543
+ begin
544
+ require filename
545
+ rescue Exception => err
546
+ msg = "Unable to require #{filename} due to #{err.message}. Ensure #{filename} is in the COSMOS lib directory."
547
+ Logger.error msg
548
+ raise msg
549
+ end
550
+ end
551
+
545
552
  # @param filename [String] Name of the file to open in the editor
546
553
  def self.open_in_text_editor(filename)
547
554
  if filename
@@ -621,10 +628,14 @@ module Cosmos
621
628
  def self.kill_thread(owner, thread, graceful_timeout = 1, timeout_interval = 0.01, hard_timeout = 1)
622
629
  if thread
623
630
  if owner and owner.respond_to? :graceful_kill
624
- owner.graceful_kill
625
- end_time = Time.now + graceful_timeout
626
- while thread.alive? && ((end_time - Time.now) > 0)
627
- sleep(timeout_interval)
631
+ if Thread.current != thread
632
+ owner.graceful_kill
633
+ end_time = Time.now + graceful_timeout
634
+ while thread.alive? && ((end_time - Time.now) > 0)
635
+ sleep(timeout_interval)
636
+ end
637
+ else
638
+ Logger.warn "Threads cannot graceful_kill themselves"
628
639
  end
629
640
  elsif owner
630
641
  Logger.info "Thread owner #{owner.class} does not support graceful_kill"