cosmos 4.0.3-java → 4.1.0-java

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 (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 +12 -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