cosmos 4.0.3 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -5
  3. data/Manifest.txt +11 -1
  4. data/README.md +3 -2
  5. data/Rakefile +18 -4
  6. data/appveyor.yml +19 -0
  7. data/cosmos.gemspec +14 -3
  8. data/data/config/cmd_tlm_server.yaml +3 -0
  9. data/data/crc.txt +63 -60
  10. data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
  11. data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
  12. data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
  13. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
  14. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
  15. data/demo/lib/cmd_sequence_exporter.rb +52 -0
  16. data/demo/lib/example_background_task.rb +1 -0
  17. data/demo/procedures/replay_test.rb +32 -0
  18. data/ext/cosmos/ext/structure/structure.c +39 -3
  19. data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
  20. data/install/config/tools/launcher/launcher.txt +2 -0
  21. data/lib/cosmos/config/config_parser.rb +2 -0
  22. data/lib/cosmos/core_ext/io.rb +89 -60
  23. data/lib/cosmos/gui/qt.rb +5 -8
  24. data/lib/cosmos/gui/qt_tool.rb +8 -8
  25. data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
  26. data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
  27. data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
  28. data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
  29. data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
  30. data/lib/cosmos/interfaces/udp_interface.rb +27 -14
  31. data/lib/cosmos/io/buffered_file.rb +0 -1
  32. data/lib/cosmos/io/json_drb.rb +134 -214
  33. data/lib/cosmos/io/json_drb_object.rb +22 -61
  34. data/lib/cosmos/io/json_drb_rack.rb +79 -0
  35. data/lib/cosmos/io/json_rpc.rb +27 -0
  36. data/lib/cosmos/io/udp_sockets.rb +102 -58
  37. data/lib/cosmos/packets/commands.rb +1 -1
  38. data/lib/cosmos/packets/structure.rb +1 -1
  39. data/lib/cosmos/packets/structure_item.rb +37 -5
  40. data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
  41. data/lib/cosmos/script/replay.rb +60 -0
  42. data/lib/cosmos/script/script.rb +20 -2
  43. data/lib/cosmos/script/scripting.rb +9 -9
  44. data/lib/cosmos/script/tools.rb +14 -0
  45. data/lib/cosmos/system/system.rb +185 -92
  46. data/lib/cosmos/system/target.rb +1 -1
  47. data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
  48. data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
  49. data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
  50. data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
  51. data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
  52. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
  53. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
  54. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
  55. data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
  56. data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
  57. data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
  58. data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
  59. data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
  60. data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
  61. data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
  62. data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
  63. data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
  64. data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
  65. data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
  66. data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
  67. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
  68. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
  69. data/lib/cosmos/tools/replay/replay.rb +5 -505
  70. data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
  71. data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
  72. data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
  73. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
  74. data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
  75. data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
  76. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
  77. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
  78. data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
  79. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
  80. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
  81. data/lib/cosmos/top_level.rb +34 -24
  82. data/lib/cosmos/utilities/csv.rb +60 -8
  83. data/lib/cosmos/version.rb +5 -5
  84. data/spec/config/config_parser_spec.rb +10 -1
  85. data/spec/core_ext/socket_spec.rb +4 -2
  86. data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
  87. data/spec/install/config/data/data.txt +1 -0
  88. data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
  89. data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
  90. data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
  91. data/spec/interfaces/serial_interface_spec.rb +1 -1
  92. data/spec/interfaces/udp_interface_spec.rb +14 -0
  93. data/spec/io/buffered_file_spec.rb +37 -0
  94. data/spec/io/json_drb_object_spec.rb +2 -15
  95. data/spec/io/json_drb_spec.rb +61 -121
  96. data/spec/io/udp_sockets_spec.rb +42 -2
  97. data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
  98. data/spec/packets/binary_accessor_spec.rb +1 -1
  99. data/spec/packets/packet_item_spec.rb +1 -1
  100. data/spec/packets/structure_item_spec.rb +5 -6
  101. data/spec/script/cmd_tlm_server_spec.rb +39 -4
  102. data/spec/script/commands_disconnect_spec.rb +1 -1
  103. data/spec/script/commands_spec.rb +2 -1
  104. data/spec/script/scripting_spec.rb +18 -3
  105. data/spec/script/telemetry_spec.rb +5 -0
  106. data/spec/spec_helper.rb +43 -26
  107. data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
  108. data/spec/system/system_spec.rb +11 -9
  109. data/spec/system/target_spec.rb +3 -0
  110. data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
  111. data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
  112. data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
  113. data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
  114. data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
  115. data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
  116. data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
  117. data/spec/tools/launcher/launcher_config_spec.rb +1 -1
  118. data/spec/tools/table_manager/table_item_spec.rb +1 -1
  119. data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
  120. data/spec/top_level/top_level_spec.rb +151 -3
  121. data/spec/utilities/csv_spec.rb +24 -5
  122. metadata +61 -9
  123. data/lib/cosmos/tools/replay/replay_server.rb +0 -91
@@ -34,6 +34,8 @@ module Cosmos
34
34
  # calling the 'call' method once.
35
35
  # @param index [Integer] Which background task to start
36
36
  def start(index)
37
+ raise "No task at index #{index}. There are #{@config.background_tasks.length} total tasks." unless index < @config.background_tasks.length
38
+ return if @threads[index] # Don't re-create a running thread. They must call stop first.
37
39
  @threads[index] = Thread.new do
38
40
  @config.background_tasks[index].thread = Thread.current
39
41
  begin
@@ -56,6 +58,7 @@ module Cosmos
56
58
  # Ruby thread.
57
59
  # @param index [Integer] Which background task to stop
58
60
  def stop(index)
61
+ raise "No task at index #{index}. There are #{@config.background_tasks.length} total tasks." unless index < @config.background_tasks.length
59
62
  begin
60
63
  @config.background_tasks[index].stop
61
64
  rescue
@@ -17,6 +17,7 @@ require 'cosmos/tools/cmd_tlm_server/commanding'
17
17
  require 'cosmos/tools/cmd_tlm_server/interfaces'
18
18
  require 'cosmos/tools/cmd_tlm_server/packet_logging'
19
19
  require 'cosmos/tools/cmd_tlm_server/routers'
20
+ require 'cosmos/tools/cmd_tlm_server/replay_backend'
20
21
 
21
22
  module Cosmos
22
23
 
@@ -39,6 +40,8 @@ module Cosmos
39
40
  instance_attr_reader :packet_logging
40
41
  # @return [Routers] Access to the routers
41
42
  instance_attr_reader :routers
43
+ # @return [ReplayBackend] Access to replay logic
44
+ instance_attr_reader :replay_backend
42
45
  # @return [MessageLog] Message log for the CmdTlmServer
43
46
  instance_attr_reader :message_log
44
47
  # @return [JsonDRb] Provides access to the server for all tools both
@@ -46,6 +49,8 @@ module Cosmos
46
49
  instance_attr_accessor :json_drb
47
50
  # @return [String] CmdTlmServer title as set in the config file
48
51
  instance_attr_accessor :title
52
+ # @return [Symbol] mode :CMD_TLM_SERVER or :REPLAY
53
+ instance_attr_accessor :mode
49
54
 
50
55
  # attr_reader attributes are only used by CmdTlmServer internally and are
51
56
  # thus only available as attributes on the singleton
@@ -70,6 +75,18 @@ module Cosmos
70
75
  # subscribe_packet_data is called. This ID must be used in the
71
76
  # packet_data_queues hash to access the queue.
72
77
  attr_accessor :next_packet_data_queue_id
78
+ # @return [Mutex] Synchronization object around server messages
79
+ attr_reader :server_message_queue_mutex
80
+ # @return [Hash<Integer, Array<Queue, Integer>>] The server message queues
81
+ # hashed by id. Returns an array containing the queue followed by the
82
+ # queue size.
83
+ attr_reader :server_message_queues
84
+ # @return [Integer] The next server message queue id when
85
+ # subscribe_server_messages is called. This ID must be used in the
86
+ # server_message_queues hash to access the queue.
87
+ attr_accessor :next_server_message_queue_id
88
+ # @return [Boolean] Whether the server was created in disconnect mode
89
+ attr_reader :disconnect
73
90
 
74
91
  # The default configuration file name
75
92
  DEFAULT_CONFIG_FILE = File.join(Cosmos::USERPATH, 'config', 'tools', 'cmd_tlm_server', 'cmd_tlm_server.txt')
@@ -79,6 +96,9 @@ module Cosmos
79
96
  # The maximum number of packets that are queued. Used when subscribing to
80
97
  # packet data.
81
98
  DEFAULT_PACKET_DATA_QUEUE_SIZE = 1000
99
+ # The maximum number of server messages that are queued. Used when subscribing to
100
+ # server messages.
101
+ DEFAULT_SERVER_MESSAGES_QUEUE_SIZE = 1000
82
102
 
83
103
  @@instance = nil
84
104
  @@meta_callback = nil
@@ -93,7 +113,8 @@ module Cosmos
93
113
  @@meta_callback = meta_callback
94
114
  end
95
115
 
96
- # Constructor for a CmdTlmServer
116
+ # Constructor for a CmdTlmServer. Initializes all internal state and
117
+ # starts up the sever
97
118
  #
98
119
  # @param config_file [String] The name of the server configuration file
99
120
  # which must be in the config/tools/cmd_tlm_server directory.
@@ -104,13 +125,16 @@ module Cosmos
104
125
  # stand-alone mode which does not actually use the interfaces to send and
105
126
  # receive data. This is useful for testing scripts when actual hardware
106
127
  # is not available.
107
- def initialize(config_file = DEFAULT_CONFIG_FILE,
108
- production = false,
109
- disconnect = false,
110
- create_message_log = true)
128
+ # @param mode [Symbol] :CMD_TLM_SERVER or :REPLAY - Defines overall mode
129
+ def initialize(
130
+ config_file = DEFAULT_CONFIG_FILE,
131
+ production = false,
132
+ disconnect = false,
133
+ mode = :CMD_TLM_SERVER)
134
+
111
135
  @@instance = self
112
136
  @packet_logging = nil # Removes warnings
113
- @message_log = MessageLog.new('server') if create_message_log
137
+ @mode = mode
114
138
 
115
139
  super() # For Api
116
140
 
@@ -121,10 +145,12 @@ module Cosmos
121
145
  @limits_event_queue_mutex = Mutex.new
122
146
  @limits_event_queues = {}
123
147
  @next_limits_event_queue_id = 1
124
-
125
148
  @packet_data_queue_mutex = Mutex.new
126
149
  @packet_data_queues = {}
127
150
  @next_packet_data_queue_id = 1
151
+ @server_message_queue_mutex = Mutex.new
152
+ @server_message_queues = {}
153
+ @next_server_message_queue_id = 1
128
154
 
129
155
  # Process cmd_tlm_server.txt
130
156
  @config = CmdTlmServerConfig.new(config_file)
@@ -133,8 +159,13 @@ module Cosmos
133
159
  @interfaces = Interfaces.new(@config, method(:identified_packet_callback))
134
160
  @packet_logging = PacketLogging.new(@config)
135
161
  @routers = Routers.new(@config)
162
+ @replay_backend = ReplayBackend.new(@config)
136
163
  @title = @config.title
164
+ if @mode != :CMD_TLM_SERVER
165
+ @title.gsub!("Command and Telemetry Server", "Replay")
166
+ end
137
167
  @stop_callback = nil
168
+ @reload_callback = nil
138
169
 
139
170
  # Set Threads to kill CTS if they throw an exception
140
171
  Thread.abort_on_exception = true
@@ -142,113 +173,98 @@ module Cosmos
142
173
  # Don't start the DRb service or the telemetry monitoring thread
143
174
  # if we started the server in disconnect mode
144
175
  @json_drb = nil
145
- start(production) unless @disconnect
146
- end # end def initialize
147
-
148
- # Start up the system by starting the JSON-RPC server, interfaces, routers,
149
- # and background tasks. Starts a thread to monitor all packets for
150
- # staleness so other tools (such as Packet Viewer or Telemetry Viewer) can
151
- # react accordingly.
152
- #
153
- # @param production (see #initialize)
154
- def start(production = false)
155
- System.telemetry # Make sure definitions are loaded by starting anything
156
- return unless @json_drb.nil?
157
-
158
- @@meta_callback.call() if @@meta_callback if @config.metadata
159
-
160
- # Start DRb with access control
161
- @json_drb = JsonDRb.new
162
- @json_drb.acl = System.acl if System.acl
163
-
164
- # In production we start logging and don't allow the user to stop it
165
- # We also disallow setting telemetry and disconnecting from interfaces
166
- if production
167
- @packet_logging.start
168
- @api_whitelist.delete('stop_logging')
169
- @api_whitelist.delete('stop_cmd_log')
170
- @api_whitelist.delete('stop_tlm_log')
171
- @interfaces.all.each do |name, interface|
172
- interface.disable_disconnect = true
173
- end
174
- @routers.all.each do |name, interface|
175
- interface.disable_disconnect = true
176
+ unless @disconnect
177
+ System.telemetry # Make sure definitions are loaded by starting anything
178
+
179
+ @@meta_callback.call() if @@meta_callback and @config.metadata
180
+
181
+ # Start DRb with access control
182
+ @json_drb = JsonDRb.new
183
+ @json_drb.acl = System.acl if System.acl
184
+
185
+ # In production we start logging and don't allow the user to stop it
186
+ # We also disallow setting telemetry and disconnecting from interfaces
187
+ if production
188
+ @api_whitelist.delete('stop_logging')
189
+ @api_whitelist.delete('stop_cmd_log')
190
+ @api_whitelist.delete('stop_tlm_log')
191
+ @interfaces.all.each do |name, interface|
192
+ interface.disable_disconnect = true
193
+ end
194
+ @routers.all.each do |name, interface|
195
+ interface.disable_disconnect = true
196
+ end
176
197
  end
177
- end
178
- @json_drb.method_whitelist = @api_whitelist
179
- begin
180
- @json_drb.start_service(System.listen_hosts['CTS_API'], System.ports['CTS_API'], self)
181
- rescue Exception
182
- # Call packet_logging shutdown here to explicitly kill the logging
183
- # threads since this CTS is not going to launch
184
- @packet_logging.shutdown
185
- raise FatalError.new("Error starting JsonDRb on port #{System.ports['CTS_API']}.\nPerhaps a Command and Telemetry Server is already running?")
186
- end
187
-
188
- @routers.add_preidentified('PREIDENTIFIED_ROUTER', System.instance.ports['CTS_PREIDENTIFIED'])
189
- @routers.add_cmd_preidentified('PREIDENTIFIED_CMD_ROUTER', System.instance.ports['CTS_CMD_ROUTER'])
190
- System.telemetry.limits_change_callback = method(:limits_change_callback)
191
- @interfaces.start
192
- @routers.start
193
- @background_tasks.start_all
194
-
195
- # Start staleness monitor thread
196
- @sleeper = Sleeper.new
197
- @staleness_monitor_thread = Thread.new do
198
+ @json_drb.method_whitelist = @api_whitelist
198
199
  begin
199
- stale = []
200
- prev_stale = []
201
- while true
202
- # The check_stale method drives System.telemetry to iterate through
203
- # the packets and mark them stale as necessary.
204
- System.telemetry.check_stale
205
-
206
- # Get all stale packets that include limits items.
207
- stale_pkts = System.telemetry.stale(true)
208
-
209
- # Send :STALE_PACKET events for all newly stale packets.
210
- stale = []
211
- stale_pkts.each do |packet|
212
- pkt_name = [packet.target_name, packet.packet_name]
213
- stale << pkt_name
214
- post_limits_event(:STALE_PACKET, pkt_name) unless prev_stale.include?(pkt_name)
215
- end
216
-
217
- # Send :STALE_PACKET_RCVD events for all packets that were stale
218
- # but are no longer stale.
219
- prev_stale.each do |pkt_name|
220
- post_limits_event(:STALE_PACKET_RCVD, pkt_name) unless stale.include?(pkt_name)
221
- end
222
- prev_stale = stale.dup
223
-
224
- broken = @sleeper.sleep(10)
225
- break if broken
200
+ if @mode == :CMD_TLM_SERVER
201
+ @json_drb.start_service(System.listen_hosts['CTS_API'], System.ports['CTS_API'], self)
202
+ else
203
+ @json_drb.start_service(System.listen_hosts['REPLAY_API'], System.ports['REPLAY_API'], self)
204
+ end
205
+ rescue Exception
206
+ # Call packet_logging shutdown here to explicitly kill the logging
207
+ # threads since this CTS is not going to launch
208
+ @packet_logging.shutdown
209
+ if @mode == :CMD_TLM_SERVER
210
+ raise FatalError.new("Error starting JsonDRb on port #{System.ports['CTS_API']}.\nPerhaps a Command and Telemetry Server is already running?")
211
+ else
212
+ raise FatalError.new("Error starting JsonDRb on port #{System.ports['REPLAY_API']}.\nPerhaps another Replay is already running?")
226
213
  end
227
- rescue Exception => err
228
- Logger.fatal "Staleness Monitor thread unexpectedly died"
229
- Cosmos.handle_fatal_exception(err)
230
214
  end
231
- end # end Thread.new
232
- end
215
+
216
+ if @mode == :CMD_TLM_SERVER
217
+ @routers.add_preidentified('PREIDENTIFIED_ROUTER', System.ports['CTS_PREIDENTIFIED'])
218
+ @routers.add_cmd_preidentified('PREIDENTIFIED_CMD_ROUTER', System.ports['CTS_CMD_ROUTER'])
219
+ else
220
+ @routers.all.clear
221
+ @routers.add_preidentified('PREIDENTIFIED_ROUTER', System.ports['REPLAY_PREIDENTIFIED'])
222
+ @routers.add_cmd_preidentified('PREIDENTIFIED_CMD_ROUTER', System.ports['REPLAY_CMD_ROUTER'])
223
+ end
224
+ System.telemetry.limits_change_callback = method(:limits_change_callback)
225
+ @routers.start
226
+
227
+ start(production)
228
+ end
229
+ end # end def initialize
233
230
 
234
231
  # Properly shuts down the command and telemetry server by stoping the
235
232
  # JSON-RPC server, background tasks, routers, and interfaces. Also kills
236
- # the packet staleness monitor thread.
233
+ # the packet staleness monitor thread. This is final and the server cannot be
234
+ # restarted, it must be recreated
237
235
  def stop
238
- # Shutdown DRb
239
- @json_drb.stop_service
236
+ # Break long pollers
237
+ @limits_event_queues.dup.each do |id, data|
238
+ queue, queue_size = @limits_event_queues.delete(id)
239
+ queue << nil if queue
240
+ end
240
241
 
241
- # Shutdown staleness monitor thread
242
- Cosmos.kill_thread(self, @staleness_monitor_thread)
242
+ @server_message_queues.dup.each do |id, data|
243
+ queue, queue_size = @server_message_queues.delete(id)
244
+ queue << nil if queue
245
+ end
246
+
247
+ @packet_data_queues.dup.each do |id, data|
248
+ queue, packets, queue_size = @packet_data_queues.delete(id)
249
+ queue << nil if queue
250
+ end
243
251
 
244
- @background_tasks.stop_all
252
+ # Shutdown DRb
253
+ @json_drb.stop_service if @json_drb
245
254
  @routers.stop
246
- @interfaces.stop
247
- @packet_logging.shutdown
255
+
256
+ if @mode == :CMD_TLM_SERVER
257
+ # Shutdown staleness monitor thread
258
+ Cosmos.kill_thread(self, @staleness_monitor_thread)
259
+
260
+ @background_tasks.stop_all
261
+ @interfaces.stop
262
+ @packet_logging.shutdown
263
+ else
264
+ @replay_backend.shutdown
265
+ end
248
266
  @stop_callback.call if @stop_callback
249
267
  @message_log.stop if @message_log
250
-
251
- @json_drb = nil
252
268
  end
253
269
 
254
270
  # Set a stop callback
@@ -256,6 +272,21 @@ module Cosmos
256
272
  @stop_callback = stop_callback
257
273
  end
258
274
 
275
+ # Reload the default configuration
276
+ def reload
277
+ @replay_backend.shutdown if @mode != :CMD_TLM_SERVER
278
+ if @reload_callback
279
+ @reload_callback.call(false)
280
+ else
281
+ System.reset
282
+ end
283
+ end
284
+
285
+ # Set a reload callback
286
+ def reload_callback= (reload_callback)
287
+ @reload_callback = reload_callback
288
+ end
289
+
259
290
  # Gracefully kill the staleness monitor thread
260
291
  def graceful_kill
261
292
  @sleeper.cancel
@@ -289,13 +320,15 @@ module Cosmos
289
320
 
290
321
  post_limits_event(:LIMITS_CHANGE, [packet.target_name, packet.packet_name, item.name, old_limits_state, item.limits.state])
291
322
 
292
- if item.limits.response
293
- begin
294
- item.limits.response.call(packet, item, old_limits_state)
295
- rescue Exception => err
296
- Logger.error "#{packet.target_name} #{packet.packet_name} #{item.name} Limits Response Exception!"
297
- Logger.error "Called with old_state = #{old_limits_state}, new_state = #{item.limits.state}"
298
- Logger.error err.formatted
323
+ if @mode == :CMD_TLM_SERVER
324
+ if item.limits.response
325
+ begin
326
+ item.limits.response.call(packet, item, old_limits_state)
327
+ rescue Exception => err
328
+ Logger.error "#{packet.target_name} #{packet.packet_name} #{item.name} Limits Response Exception!"
329
+ Logger.error "Called with old_state = #{old_limits_state}, new_state = #{item.limits.state}"
330
+ Logger.error err.formatted
331
+ end
299
332
  end
300
333
  end
301
334
  end
@@ -352,6 +385,10 @@ module Cosmos
352
385
  # @return [Integer] The queue ID returned from the CmdTlmServer. Use this
353
386
  # ID when calling {#get_limits_event} and {#unsubscribe_limits_events}.
354
387
  def self.subscribe_limits_events(queue_size = DEFAULT_LIMITS_EVENT_QUEUE_SIZE)
388
+ unless queue_size.is_a? Integer and queue_size > 0
389
+ raise ArgumentError, "Invalid queue size for subscribe_limits_events: #{queue_size}"
390
+ end
391
+
355
392
  id = nil
356
393
  @@instance.limits_event_queue_mutex.synchronize do
357
394
  id = @@instance.next_limits_event_queue_id
@@ -461,12 +498,22 @@ module Cosmos
461
498
  upcase_packets = []
462
499
 
463
500
  # Upper case packet names
501
+ need_meta = false
464
502
  packets.length.times do |index|
465
503
  upcase_packets << []
466
504
  upcase_packets[index][0] = packets[index][0].upcase
467
505
  upcase_packets[index][1] = packets[index][1].upcase
506
+
468
507
  # Get the packet to ensure it exists
469
- @@instance.get_tlm_packet(upcase_packets[index][0], upcase_packets[index][1])
508
+ if @@instance.disconnect
509
+ @last_subscribed_packet = System.telemetry.packet(upcase_packets[index][0], upcase_packets[index][1])
510
+ else
511
+ @@instance.get_tlm_packet(upcase_packets[index][0], upcase_packets[index][1])
512
+ end
513
+
514
+ if upcase_packets[index][0] == 'SYSTEM' and upcase_packets[index][1] == 'META'
515
+ need_meta = true
516
+ end
470
517
  end
471
518
 
472
519
  @@instance.packet_data_queue_mutex.synchronize do
@@ -474,6 +521,15 @@ module Cosmos
474
521
  @@instance.packet_data_queues[id] =
475
522
  [Queue.new, upcase_packets, queue_size]
476
523
  @@instance.next_packet_data_queue_id += 1
524
+
525
+ # Send the current meta packet first if requested
526
+ if need_meta
527
+ packet = System.telemetry.packet('SYSTEM', 'META')
528
+ received_time = packet.received_time
529
+ received_time ||= Time.now.sys
530
+ @@instance.packet_data_queues[id][0] << [packet.buffer, 'SYSTEM', 'META',
531
+ received_time.tv_sec, received_time.tv_usec, packet.received_count]
532
+ end
477
533
  end
478
534
  return id
479
535
  end
@@ -509,17 +565,117 @@ module Cosmos
509
565
  queue, _, _ = @@instance.packet_data_queues[id]
510
566
  end
511
567
  if queue
512
- return queue.pop(non_block)
568
+ if @@instance.disconnect
569
+ begin
570
+ return queue.pop(true)
571
+ rescue ThreadError
572
+ received_time ||= Time.now.sys
573
+ return [@last_subscribed_packet.buffer, @last_subscribed_packet.target_name,
574
+ @last_subscribed_packet.packet_name, received_time.tv_sec, received_time.tv_usec, @last_subscribed_packet.received_count]
575
+ end
576
+ else
577
+ return queue.pop(non_block)
578
+ end
513
579
  else
514
580
  raise "Packet data queue with id #{id} not found"
515
581
  end
516
582
  end
517
583
 
584
+ # Post a server message to all subscribed server message listeners.
585
+ # Messages are formatted as [Text, Color], e.g. ["Msg1","RED"]
586
+ #
587
+ # @param message [Array<String, String>] Server message
588
+ def post_server_message(message)
589
+ if @server_message_queues.length > 0
590
+ queues_to_drop = []
591
+
592
+ @server_message_queue_mutex.synchronize do
593
+ # Post event to active queues
594
+ @server_message_queues.each do |id, data|
595
+ queue = data[0]
596
+ queue_size = data[1]
597
+ queue << message
598
+ if queue.length > queue_size
599
+ # Drop queue
600
+ queues_to_drop << id
601
+ end
602
+ end
603
+
604
+ # Drop queues which are not being serviced
605
+ queues_to_drop.each do |id|
606
+ # Remove the queue to stop servicing it. Nil is added to unblock any client threads
607
+ # that might otherwise be left blocking forever for something on the queue
608
+ queue, queue_size = @server_message_queues.delete(id)
609
+ queue << nil if queue
610
+ end
611
+ end
612
+ end
613
+ end
614
+
615
+ # Create a queue on the CmdTlmServer that gets populated with every message
616
+ # in the system.
617
+ #
618
+ # @param queue_size [Integer] The number of server messages to accumulate
619
+ # before the queue will be dropped due to inactivity.
620
+ # @return [Integer] The queue ID returned from the CmdTlmServer. Use this
621
+ # ID when calling {#get_server_message} and {#unsubscribe_server_messages}.
622
+ def self.subscribe_server_messages(queue_size = DEFAULT_SERVER_MESSAGES_QUEUE_SIZE)
623
+ unless queue_size.is_a? Integer and queue_size > 0
624
+ raise ArgumentError, "Invalid queue size for subscribe_server_messages: #{queue_size}"
625
+ end
626
+
627
+ id = nil
628
+ @@instance.server_message_queue_mutex.synchronize do
629
+ id = @@instance.next_server_message_queue_id
630
+ @@instance.server_message_queues[id] = [Queue.new, queue_size]
631
+ @@instance.next_server_message_queue_id += 1
632
+ end
633
+ return id
634
+ end
635
+
636
+ # Unsubscribe from being notified for every server message in the system.
637
+ # This deletes the queue and further calls to {#get_server_message} will
638
+ # raise an exception.
639
+ #
640
+ # @param id [Integer] The queue ID received from calling
641
+ # {#subscribe_server_messages}
642
+ def self.unsubscribe_server_messages(id)
643
+ queue = nil
644
+ @@instance.server_message_queue_mutex.synchronize do
645
+ # Remove the queue to stop servicing it. Nil is added to unblock any client threads
646
+ # that might otherwise be left blocking forever for something on the queue
647
+ queue, queue_size = @@instance.server_message_queues.delete(id)
648
+ queue << nil if queue
649
+ end
650
+ end
651
+
652
+ # Get a server message from the queue created by {#subscribe_server_messages}.
653
+ #
654
+ # Each server message consists of a String
655
+ #
656
+ # @param id [Integer] The queue ID received from calling
657
+ # {#subscribe_server_messages}
658
+ # @param non_block [Boolean] Whether to wait on the queue for the next
659
+ # server message before returning. Default is to block waiting for the next
660
+ # message. NOTE: If you pass true and there is no data on the queue, a
661
+ # ThreadError exception is raised.
662
+ def self.get_server_message(id, non_block = false)
663
+ queue = nil
664
+ @@instance.server_message_queue_mutex.synchronize do
665
+ queue, _ = @@instance.server_message_queues[id]
666
+ end
667
+ if queue
668
+ return queue.pop(non_block)
669
+ else
670
+ raise "Server message queue with id #{id} not found"
671
+ end
672
+ end
673
+
518
674
  # Calls clear_counters on the System, interfaces, routers, and sets the
519
675
  # request_count on json_drb to 0.
520
676
  def self.clear_counters
521
677
  System.clear_counters
522
- self.instance.interfaces.clear_counters
678
+ self.instance.interfaces.clear_counters if self.instance.interfaces
523
679
  self.instance.routers.clear_counters
524
680
  self.instance.json_drb.request_count = 0
525
681
  end
@@ -533,5 +689,67 @@ module Cosmos
533
689
  packet.check_limits(System.limits_set)
534
690
  post_packet(packet)
535
691
  end
692
+
693
+ private
694
+
695
+ # Start up the system by starting the JSON-RPC server, interfaces, routers,
696
+ # and background tasks. Starts a thread to monitor all packets for
697
+ # staleness so other tools (such as Packet Viewer or Telemetry Viewer) can
698
+ # react accordingly.
699
+ #
700
+ # This method is shoudl only called by initialize which is why it is private
701
+ #
702
+ # @param start_packet_logging [Boolean] Whether to start logging data or not
703
+ def start(start_packet_logging = false)
704
+ if @mode == :CMD_TLM_SERVER
705
+ @replay_backend = nil # Remove access to Replay
706
+ @message_log = MessageLog.new('server')
707
+ @packet_logging.start if start_packet_logging
708
+ @interfaces.start
709
+ @background_tasks.start_all
710
+
711
+ # Start staleness monitor thread
712
+ @sleeper = Sleeper.new
713
+ @staleness_monitor_thread = Thread.new do
714
+ begin
715
+ stale = []
716
+ prev_stale = []
717
+ while true
718
+ # The check_stale method drives System.telemetry to iterate through
719
+ # the packets and mark them stale as necessary.
720
+ System.telemetry.check_stale
721
+
722
+ # Get all stale packets that include limits items.
723
+ stale_pkts = System.telemetry.stale(true)
724
+
725
+ # Send :STALE_PACKET events for all newly stale packets.
726
+ stale = []
727
+ stale_pkts.each do |packet|
728
+ pkt_name = [packet.target_name, packet.packet_name]
729
+ stale << pkt_name
730
+ post_limits_event(:STALE_PACKET, pkt_name) unless prev_stale.include?(pkt_name)
731
+ end
732
+
733
+ # Send :STALE_PACKET_RCVD events for all packets that were stale
734
+ # but are no longer stale.
735
+ prev_stale.each do |pkt_name|
736
+ post_limits_event(:STALE_PACKET_RCVD, pkt_name) unless stale.include?(pkt_name)
737
+ end
738
+ prev_stale = stale.dup
739
+
740
+ broken = @sleeper.sleep(10)
741
+ break if broken
742
+ end
743
+ rescue Exception => err
744
+ Logger.fatal "Staleness Monitor thread unexpectedly died"
745
+ Cosmos.handle_fatal_exception(err)
746
+ end
747
+ end # end Thread.new
748
+ else
749
+ # Prevent access to interfaces or packet_logging
750
+ @interfaces = nil
751
+ @packet_logging = nil
752
+ end
753
+ end
536
754
  end
537
755
  end