cosmos 3.1.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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"