cosmos 4.1.0 → 4.1.1

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