cosmos 4.1.0 → 4.1.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +5 -0
  3. data/appveyor.yml +2 -0
  4. data/autohotkey/tools/replay.ahk +45 -45
  5. data/autohotkey/tools/script_runner.ahk +3 -9
  6. data/cosmos.gemspec +1 -1
  7. data/data/config/interface_modifiers.yaml +23 -0
  8. data/data/config/screen.yaml +1 -1
  9. data/data/crc.txt +20 -18
  10. data/demo/config/targets/INST/cmd_tlm_server.txt +1 -1
  11. data/lib/cosmos/config/config_parser.rb +8 -3
  12. data/lib/cosmos/gui/dialogs/exception_dialog.rb +20 -5
  13. data/lib/cosmos/interfaces/protocols/burst_protocol.rb +13 -3
  14. data/lib/cosmos/interfaces/protocols/crc_protocol.rb +27 -3
  15. data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +4 -2
  16. data/lib/cosmos/interfaces/protocols/length_protocol.rb +4 -2
  17. data/lib/cosmos/interfaces/protocols/override_protocol.rb +2 -2
  18. data/lib/cosmos/interfaces/protocols/preidentified_protocol.rb +3 -2
  19. data/lib/cosmos/interfaces/protocols/protocol.rb +16 -4
  20. data/lib/cosmos/interfaces/protocols/template_protocol.rb +7 -2
  21. data/lib/cosmos/interfaces/protocols/terminated_protocol.rb +4 -2
  22. data/lib/cosmos/packets/packet_config.rb +19 -859
  23. data/lib/cosmos/packets/packet_item.rb +56 -201
  24. data/lib/cosmos/packets/parsers/xtce_converter.rb +440 -0
  25. data/lib/cosmos/packets/parsers/xtce_parser.rb +682 -0
  26. data/lib/cosmos/tools/config_editor/config_editor.rb +143 -5
  27. data/lib/cosmos/tools/tlm_viewer/screen.rb +1 -1
  28. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +5 -3
  29. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +40 -27
  30. data/lib/cosmos/version.rb +4 -4
  31. data/spec/config/config_parser_spec.rb +39 -2
  32. data/spec/install/config/targets/INST/screens/hs.txt +42 -0
  33. data/spec/install/config/targets/INST/target.txt +2 -0
  34. data/spec/interfaces/protocols/burst_protocol_spec.rb +18 -0
  35. data/spec/interfaces/protocols/length_protocol_spec.rb +49 -0
  36. data/spec/interfaces/udp_interface_spec.rb +0 -9
  37. data/spec/packets/packet_config_spec.rb +21 -144
  38. data/spec/packets/packet_item_spec.rb +68 -4
  39. data/spec/packets/parsers/packet_item_parser_spec.rb +12 -0
  40. data/spec/packets/parsers/xtce_parser_spec.rb +398 -0
  41. data/spec/tools/tlm_viewer/tlm_viewer_config_spec.rb +401 -0
  42. metadata +9 -10
@@ -17,10 +17,18 @@ Cosmos.catch_fatal_exception do
17
17
  end
18
18
 
19
19
  module Cosmos
20
-
21
20
  class ConfigEditor < QtTool
21
+ # Class to intercept keyPressEvents
22
+ class MyTreeView < Qt::TreeView
23
+ attr_accessor :keyPressCallback
24
+ def keyPressEvent(event)
25
+ call_super = @keyPressCallback.call(event)
26
+ super(event) if call_super
27
+ end
28
+ end
29
+
22
30
  slots 'handle_tab_change(int)'
23
- slots 'context_menu(const QPoint&)'
31
+ slots 'tab_context_menu(const QPoint&)'
24
32
  slots 'undo_available(bool)'
25
33
 
26
34
  UNTITLED = 'Untitled'
@@ -211,6 +219,10 @@ module Cosmos
211
219
  active_config_editor_frame.set_file_type(action.text)
212
220
  update_cursor()
213
221
  end
222
+
223
+ @create_target = Qt::Action.new(tr('&Create Target'), self)
224
+ @create_target.statusTip = tr('Create a new COSMOS target')
225
+ @create_target.connect(SIGNAL('triggered()')) { create_target() }
214
226
  end
215
227
 
216
228
  def initialize_menus
@@ -259,6 +271,10 @@ module Cosmos
259
271
  type_menu = menuBar.addMenu(tr('File &Type'))
260
272
  type_menu.addActions(@type_group.actions)
261
273
 
274
+ # Actions Menu
275
+ actions_menu = menuBar.addMenu(tr('&Actions'))
276
+ actions_menu.addAction(@create_target)
277
+
262
278
  # Help Menu
263
279
  @about_string = "Config Editor allows the user to edit COSMOS configuration "\
264
280
  "files with contextual help. "\
@@ -272,7 +288,7 @@ module Cosmos
272
288
 
273
289
  @fs_model = Qt::FileSystemModel.new
274
290
  @fs_model.setRootPath(Cosmos::USERPATH)
275
- @tree_view = Qt::TreeView.new(@splitter)
291
+ @tree_view = MyTreeView.new(@splitter)
276
292
  @tree_view.setModel(@fs_model)
277
293
  @tree_view.setRootIndex(@fs_model.index(Cosmos::USERPATH))
278
294
  @tree_view.setColumnHidden(1, true) # Size
@@ -284,6 +300,17 @@ module Cosmos
284
300
  @tree_view.connect(SIGNAL('clicked(const QModelIndex&)')) do |index|
285
301
  select_or_load_file(@fs_model.filePath(index))
286
302
  end
303
+ @tree_view.setContextMenuPolicy(Qt::CustomContextMenu)
304
+ @tree_view.connect(SIGNAL('customContextMenuRequested(const QPoint&)')) do |point|
305
+ tree_context_menu(point)
306
+ end
307
+ @tree_view.keyPressCallback = lambda do |event|
308
+ case event.key
309
+ when Qt::Key_Delete
310
+ delete_path(@fs_model.filePath(@tree_view.currentIndex()))
311
+ end
312
+ true # call super
313
+ end
287
314
 
288
315
  @tab_book = Qt::TabWidget.new(@splitter)
289
316
  @tab_book.setMovable(true)
@@ -291,7 +318,7 @@ module Cosmos
291
318
  connect(@tab_book,
292
319
  SIGNAL('customContextMenuRequested(const QPoint&)'),
293
320
  self,
294
- SLOT('context_menu(const QPoint&)'))
321
+ SLOT('tab_context_menu(const QPoint&)'))
295
322
  connect(@tab_book,
296
323
  SIGNAL('currentChanged(int)'),
297
324
  self,
@@ -340,6 +367,29 @@ module Cosmos
340
367
  end
341
368
  end
342
369
 
370
+ def tree_context_menu(point)
371
+ menu = Qt::Menu.new()
372
+
373
+ delete_action = Qt::Action.new(tr("Delete"), self)
374
+ delete_action.statusTip = tr("Delete file")
375
+ delete_action.connect(SIGNAL('triggered()')) do
376
+ delete_path(@fs_model.filePath(@tree_view.indexAt(point)))
377
+ end
378
+ menu.addAction(delete_action)
379
+
380
+ menu.exec(@tree_view.mapToGlobal(point))
381
+ menu.dispose
382
+ end
383
+
384
+ def delete_path(path)
385
+ case Qt::MessageBox.warning(self, "Delete!", "Are you sure you want to delete #{path}?",
386
+ Qt::MessageBox::Yes | Qt::MessageBox::No, # buttons
387
+ Qt::MessageBox::Yes) # default button
388
+ when Qt::MessageBox::Yes
389
+ FileUtils.rm_rf path
390
+ end
391
+ end
392
+
343
393
  ###########################################
344
394
  # File Menu Options
345
395
  ###########################################
@@ -381,6 +431,7 @@ module Cosmos
381
431
  @procedure_dir = File.dirname(filename)
382
432
  @procedure_dir << '/' if @procedure_dir[-1..-1] != '/' and @procedure_dir[-1..-1] != '\\'
383
433
  end
434
+ update_tree()
384
435
  end
385
436
 
386
437
  # File->Reload
@@ -432,6 +483,7 @@ module Cosmos
432
483
  File.open(filename, "w") {|file| file.write(active_config_editor_frame().text)}
433
484
  saved = true
434
485
  update_title()
486
+ update_tree()
435
487
  statusBar.showMessage(tr("#{filename} saved"))
436
488
  @procedure_dir = File.dirname(filename)
437
489
  @procedure_dir << '/' if @procedure_dir[-1..-1] != '/' and @procedure_dir[-1..-1] != '\\'
@@ -456,6 +508,92 @@ module Cosmos
456
508
  end
457
509
  end
458
510
 
511
+ ###########################################
512
+ # Actions
513
+ ###########################################
514
+
515
+ def create_target()
516
+ qt_boolean = Qt::Boolean.new
517
+ target = Qt::InputDialog::getText(self, "Target Name",
518
+ "Enter the name of the target.\nUse underscores to separate words.\n"\
519
+ "Try to keep target names relatively short.\n\nFor example: PWR_SUPPLY\n",
520
+ Qt::LineEdit::Normal, '', qt_boolean)
521
+ return if qt_boolean.nil? # Cancelled
522
+ target.upcase!
523
+ target_folder = File.join(Cosmos::USERPATH, 'config', 'targets', target)
524
+ if File.exist?(target_folder)
525
+ Qt::MessageBox.warning(self, "Existing Target", "The specified target already exists!")
526
+ return
527
+ end
528
+ Dir.mkdir(target_folder)
529
+ %w(cmd_tlm lib procedures screens sequences tables tools).each do |folder|
530
+ Dir.mkdir File.join(target_folder, folder)
531
+ end
532
+ File.open(File.join(target_folder, 'cmd_tlm', 'cmd.txt'), 'w') do |file|
533
+ file.puts "COMMAND #{target} NOOP BIG_ENDIAN \"No operation\""
534
+ file.puts " APPEND_ID_PARAMETER OPCODE 16 UINT 0x0 0xFFFF 0x1 \"Noop opcode\""
535
+ end
536
+ File.open(File.join(target_folder, 'cmd_tlm', 'tlm.txt'), 'w') do |file|
537
+ file.puts "TELEMETRY #{target} STATUS BIG_ENDIAN \"Status telemetry\""
538
+ file.puts " APPEND_ID_ITEM OPCODE 16 UINT 0x1 \"Opcode which identifies this packet\""
539
+ file.puts " APPEND_ITEM COUNT 16 UINT \"Packet counter\""
540
+ file.puts " APPEND_ITEM VALUE 0 STRING \"String which consumes the rest of the packet\""
541
+ end
542
+ lib_filename = File.join(target_folder, 'lib', "#{target.downcase}.rb")
543
+ File.open(lib_filename, 'w') do |file|
544
+ file.puts "require 'cosmos'"
545
+ file.puts "\n"
546
+ file.puts "class #{lib_filename.filename_to_class_name}"
547
+ file.puts " attr_reader :tgt_name"
548
+ file.puts "\n"
549
+ file.puts " # It is good practice to pass in the name of the target when instantiating"
550
+ file.puts " # the library. This way if the target name changes by COSMOS target name"
551
+ file.puts " # substitution or by simply renaming in the filesystem, the library continues"
552
+ file.puts " # to work simply by passing in the new name."
553
+ file.puts " def initialize(tgt_name = '#{target}')"
554
+ file.puts " @tgt_name = tgt_name"
555
+ file.puts " end"
556
+ file.puts "\n"
557
+ file.puts " def noop"
558
+ file.puts " count = tlm(\"\#{@tgt_name} STATUS COUNT\")"
559
+ file.puts " cmd(\"\#{@tgt_name} NOOP\")"
560
+ file.puts " # Wait 5s for the counter to increment. Note the double equals!"
561
+ file.puts " wait_check(\"\#{@tgt_name} STATUS COUNT == \#{count + 1}\", 5)"
562
+ file.puts " end"
563
+ file.puts "end"
564
+ end
565
+ File.open(File.join(target_folder, 'procedures', "#{target.downcase}_noop.rb"), 'w') do |file|
566
+ file.puts "require 'cosmos'"
567
+ file.puts "require '#{File.basename(lib_filename)}'"
568
+ file.puts "\n"
569
+ file.puts "#{target.downcase} = #{lib_filename.filename_to_class_name}.new"
570
+ file.puts "#{target.downcase}.noop"
571
+ end
572
+ File.open(File.join(target_folder, 'target.txt'), 'w') do |file|
573
+ file.puts "# Ignored Parameters"
574
+ file.puts "IGNORE_PARAMETER OPCODE"
575
+ file.puts "\n"
576
+ file.puts "# Ignored Items"
577
+ file.puts "IGNORE_ITEM OPCODE"
578
+ file.puts "\n"
579
+ file.puts "# Automatically substitute the target name in screen definitions"
580
+ file.puts "AUTO_SCREEN_SUBSTITUTE"
581
+ end
582
+ cmd_tlm = File.join(target_folder, 'cmd_tlm_server.txt')
583
+ File.open(cmd_tlm, 'w') do |file|
584
+ file.puts "# This is a segment of the main cmd_tlm_server.txt that will be used with"
585
+ file.puts "# AUTO_INTERFACE_TARGETS or INTERFACE_TARGET"
586
+ file.puts "\n"
587
+ file.puts "# NOTE: This line must be modified to match how your actual target connects."
588
+ file.puts "# See http://cosmosrb.com/docs/interfaces/ for more information."
589
+ file.puts "INTERFACE #{target}_INT tcpip_client_interface.rb localhost 8080 8080 10.0 nil BURST 4 0xDEADBEEF"
590
+ file.puts " TARGET #{target}"
591
+ file.puts " # Add in the OverrideProtocol to allow override_tlm(\"#{target} STATUS STRING = 'HI'\")"
592
+ file.puts " PROTOCOL READ_WRITE OverrideProtocol"
593
+ end
594
+ file_open(cmd_tlm)
595
+ end
596
+
459
597
  ###########################################
460
598
  # Callbacks
461
599
  ###########################################
@@ -537,7 +675,7 @@ module Cosmos
537
675
  end
538
676
  end
539
677
 
540
- def context_menu(point)
678
+ def tab_context_menu(point)
541
679
  index = 0
542
680
  @tab_book.tabBar.count.times do
543
681
  break if @tab_book.tabBar.tabRect(index).contains(point)
@@ -328,7 +328,7 @@ module Cosmos
328
328
  rescue Exception => err
329
329
  begin
330
330
  raise $!, "In file #{filename} at line #{parser.line_number}:\n\n#{$!}", $!.backtrace
331
- rescue => err
331
+ rescue Exception => err
332
332
  ExceptionDialog.new(self, err, "Screen #{File.basename(filename)}", false)
333
333
  end
334
334
  shutdown()
@@ -184,7 +184,7 @@ module Cosmos
184
184
 
185
185
  @replay_action = Qt::Action.new(tr('Toggle Replay Mode'), self)
186
186
  @replay_action.statusTip = tr('Toggle Replay Mode')
187
- @replay_action.connect(SIGNAL('triggered()')) { toggle_replay_mode() }
187
+ @replay_action.connect(SIGNAL('triggered()')) { toggle_replay_mode() }
188
188
  end
189
189
 
190
190
  def initialize_menus(options)
@@ -193,7 +193,7 @@ module Cosmos
193
193
  @file_menu.addAction(@file_save)
194
194
  @file_menu.addAction(@file_generate)
195
195
  @file_menu.addAction(@file_audit)
196
- @file_menu.addAction(@replay_action)
196
+ @file_menu.addAction(@replay_action)
197
197
  @file_menu.addSeparator()
198
198
  @file_menu.addAction(@exit_action)
199
199
 
@@ -382,7 +382,9 @@ module Cosmos
382
382
  found = []
383
383
  all_telemetry.each do |tlm|
384
384
  break if @cancel_audit
385
- found << tlm if screen_text.include? tlm
385
+ if screen_text.include?(tlm) || Packet::RESERVED_ITEM_NAMES.include?(tlm.split[-1].strip)
386
+ found << tlm
387
+ end
386
388
  end
387
389
  all_telemetry -= found
388
390
 
@@ -11,9 +11,11 @@
11
11
  require 'cosmos'
12
12
 
13
13
  module Cosmos
14
-
14
+ # Parses the Telemetry Viewer configuration file and builds up the list
15
+ # of available screens to display.
15
16
  class TlmViewerConfig
16
-
17
+ # Aggregates information about a single Telemetry Viewer screen. It loads
18
+ # the widgets called out by the screen and manages NAMED_WIDGETs.
17
19
  class ScreenInfo
18
20
  attr_accessor :group
19
21
  attr_accessor :target_name
@@ -47,12 +49,12 @@ module Cosmos
47
49
  end
48
50
 
49
51
  def as_json(options = nil) #:nodoc:
50
- {group: @group,
51
- target_name: @target_name,
52
- original_target_name:
53
- @original_target_name,
54
- name: @name,
55
- filename: @filename,
52
+ {group: @group,
53
+ target_name: @target_name,
54
+ original_target_name:
55
+ @original_target_name,
56
+ name: @name,
57
+ filename: @filename,
56
58
  x_pos: @x_pos,
57
59
  y_pos: @y_pos,
58
60
  substitute: @substitute,
@@ -105,10 +107,15 @@ module Cosmos
105
107
  end
106
108
  end
107
109
 
110
+ # @return [Array<Hash]>] Columns of screen names
108
111
  attr_accessor :columns
112
+ # @return [Hash] ScreenInfo instances indexed by screen name
109
113
  attr_accessor :screen_infos
114
+ # @return [String] Name of the telemetry viewer configuration file
110
115
  attr_accessor :filename
116
+ # @return [Array] List of all the items on all the defined telemety screens
111
117
  attr_accessor :completion_list
118
+ # @return [Hash] Telemetry item to screen name lookup
112
119
  attr_accessor :tlm_to_screen_mapping
113
120
 
114
121
  def initialize(filename = nil, skip_read_items = false)
@@ -130,6 +137,7 @@ module Cosmos
130
137
  case keyword
131
138
 
132
139
  when 'NEW_COLUMN'
140
+ parser.verify_num_parameters(0, 0, 'NEW_COLUMN')
133
141
  @columns << {}
134
142
  @current_column = @columns[-1]
135
143
 
@@ -205,24 +213,31 @@ module Cosmos
205
213
 
206
214
  File.open(filename, 'w') do |file|
207
215
  @columns.each_with_index do |target_screen_infos, column_index|
208
- if column_index != 0
209
- file.puts ''
210
- file.puts "NEW_COLUMN"
211
- file.puts ''
212
- end
216
+ file.puts "NEW_COLUMN\n\n" if column_index != 0
213
217
  target_screen_infos.each do |target_name, screen_infos|
214
- file.puts "TARGET \"#{target_name}\""
218
+ if screen_infos.values[0].group
219
+ file.puts "GROUP \"#{target_name}\""
220
+ else
221
+ file.puts "TARGET \"#{target_name}\""
222
+ end
215
223
  screen_infos.each do |screen_name, screen_info|
216
- # Grab the filename by indexing the full path for 'screens' and going past
217
- # to capture the filename such as 'status.txt' below
218
- # C:/COSMOS/config/targets/TGT/screens/status.txt
219
- screen_filename = screen_info.filename[(screen_info.filename.index("screens").to_i + 8)..-1]
220
- string = " SCREEN"
221
- string << " \"#{screen_filename}\""
222
- string << " #{screen_info.x_pos}" if screen_info.x_pos
223
- string << " #{screen_info.y_pos}" if screen_info.y_pos
224
- file.puts string
225
- if screen_info.screen
224
+ if screen_info.group
225
+ string = " GROUP_SCREEN #{screen_info.name}"
226
+ string << " #{screen_info.x_pos}" if screen_info.x_pos
227
+ string << " #{screen_info.y_pos}" if screen_info.y_pos
228
+ file.puts string
229
+ else
230
+ # Grab the filename by indexing the full path for 'screens' and going past
231
+ # to capture the filename such as 'status.txt' below
232
+ # C:/COSMOS/config/targets/TGT/screens/status.txt
233
+ screen_filename = screen_info.filename[(screen_info.filename.index("screens").to_i + 8)..-1]
234
+ string = " SCREEN"
235
+ string << " \"#{screen_filename}\""
236
+ string << " #{screen_info.x_pos}" if screen_info.x_pos
237
+ string << " #{screen_info.y_pos}" if screen_info.y_pos
238
+ file.puts string
239
+ end
240
+ if screen_info.show_on_startup
226
241
  file.puts " SHOW_ON_STARTUP"
227
242
  end
228
243
  end
@@ -297,7 +312,5 @@ module Cosmos
297
312
  end
298
313
  @completion_list.uniq!
299
314
  end
300
-
301
315
  end
302
-
303
- end # module Cosmos
316
+ end
@@ -1,12 +1,12 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- COSMOS_VERSION = '4.1.0'
3
+ COSMOS_VERSION = '4.1.1'
4
4
  module Cosmos
5
5
  module Version
6
6
  MAJOR = '4'
7
7
  MINOR = '1'
8
- PATCH = '0'
9
- BUILD = '9731591f6e6dd90749a966e06a95d923b7f112ae'
8
+ PATCH = '1'
9
+ BUILD = '4d061cac0c17e02bfbb77c8506ee17354ef8b8f1'
10
10
  end
11
- VERSION = '4.1.0'
11
+ VERSION = '4.1.1'
12
12
  end
@@ -12,6 +12,7 @@ require 'spec_helper'
12
12
  require 'cosmos'
13
13
  require 'cosmos/config/config_parser'
14
14
  require 'tempfile'
15
+ require 'tmpdir'
15
16
 
16
17
  module Cosmos
17
18
 
@@ -76,10 +77,46 @@ module Cosmos
76
77
  tf.unlink
77
78
  end
78
79
 
80
+ it "allows ERB partials in subdirectories" do
81
+ Dir.mktmpdir("partial_dir") do |dir|
82
+ tf2 = Tempfile.new('_partial.txt', dir)
83
+ tf2.puts "SUBDIR"
84
+ tf2.close
85
+ tf = Tempfile.new('unittest')
86
+ # Grab the sub directory name plus filename
87
+ subdir_path = tf2.path().split('/')[-2..-1].join('/')
88
+ tf.puts "<%= render '#{subdir_path}' %>"
89
+ tf.close
90
+
91
+ @cp.parse_file(tf.path) do |keyword, params|
92
+ expect(keyword).to eql "SUBDIR"
93
+ end
94
+ tf.unlink
95
+ tf2.unlink
96
+ end
97
+ end
98
+
99
+ it "allows absolute paths to ERB partials" do
100
+ Dir.mktmpdir("partial_dir") do |dir|
101
+ tf2 = Tempfile.new('_partial.txt', dir)
102
+ tf2.puts "ABSOLUTE"
103
+ tf2.close
104
+ tf = Tempfile.new('unittest')
105
+ tf.puts "<%= render '#{tf2.path}' %>"
106
+ tf.close
107
+
108
+ @cp.parse_file(tf.path) do |keyword, params|
109
+ expect(keyword).to eql "ABSOLUTE"
110
+ end
111
+ tf.unlink
112
+ tf2.unlink
113
+ end
114
+ end
115
+
79
116
  it "supports ERB partials via render" do
80
117
  tf2 = Tempfile.new('_partial.txt')
81
118
  tf2.puts '<% if output %>'
82
- tf2.puts 'KEYWORD <%= id %> <%= desc %>'
119
+ tf2.puts 'RENDER <%= id %> <%= desc %>'
83
120
  tf2.puts '<% end %>'
84
121
  tf2.close
85
122
 
@@ -92,7 +129,7 @@ module Cosmos
92
129
  yielded = false
93
130
  @cp.parse_file(tf.path) do |keyword, params|
94
131
  yielded = true
95
- expect(keyword).to eql "KEYWORD"
132
+ expect(keyword).to eql "RENDER"
96
133
  expect(params[0]).to eql "1"
97
134
  expect(params[1]).to eql "Description"
98
135
  end