cosmos 4.4.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/.dockerignore +2 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +6 -6
  5. data/Dockerfile +65 -0
  6. data/Manifest.txt +12 -2
  7. data/README.md +5 -0
  8. data/Rakefile +52 -0
  9. data/appveyor.yml +18 -8
  10. data/autohotkey/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
  11. data/autohotkey/lib/cmd_sequence_exporter.rb +52 -0
  12. data/autohotkey/procedures/collect.rb +2 -2
  13. data/autohotkey/procedures/collect_util.rb +1 -1
  14. data/autohotkey/procedures/script_test.rb +1 -1
  15. data/autohotkey/tools/CmdSenderAHK2 +18 -0
  16. data/autohotkey/tools/cmd_sender.ahk +34 -6
  17. data/autohotkey/tools/cmd_sender2.ahk +4 -0
  18. data/autohotkey/tools/cmd_sequence.ahk +21 -8
  19. data/autohotkey/tools/config_editor.ahk +4 -4
  20. data/bin/cstol_converter +1 -1
  21. data/cosmos.gemspec +1 -1
  22. data/data/config/command_modifiers.yaml +16 -1
  23. data/data/config/param_item_modifiers.yaml +5 -0
  24. data/data/config/system.yaml +31 -1
  25. data/data/config/telemetry_modifiers.yaml +16 -1
  26. data/data/crc.txt +415 -410
  27. data/demo/config/dart/Gemfile +1 -6
  28. data/demo/config/data/crc.txt +244 -243
  29. data/demo/config/system/system.txt +3 -0
  30. data/demo/config/system/system2.txt +3 -0
  31. data/demo/config/system/system_alt_ports.txt +3 -0
  32. data/demo/config/targets/INST/cmd_tlm/inst_cmds.txt +3 -3
  33. data/demo/config/targets/INST/cmd_tlm/inst_tlm.txt +4 -0
  34. data/demo/config/targets/INST/cmd_tlm/inst_tlm_override.txt +12 -0
  35. data/demo/config/targets/INST/lib/sim_inst.rb +2 -2
  36. data/demo/config/targets/INST/target.txt +1 -0
  37. data/demo/procedures/cosmos_api_test.rb +8 -8
  38. data/install/config/dart/Gemfile +2 -7
  39. data/install/config/data/crc.txt +143 -143
  40. data/install/config/system/system.txt +3 -0
  41. data/lib/cosmos/dart/config/boot.rb +1 -1
  42. data/lib/cosmos/dart/config/database.yml +2 -0
  43. data/lib/cosmos/dart/lib/dart_common.rb +11 -4
  44. data/lib/cosmos/dart/lib/dart_constants.rb +15 -0
  45. data/lib/cosmos/dart/lib/dart_decom_query.rb +5 -6
  46. data/lib/cosmos/dart/lib/dart_decommutator.rb +66 -56
  47. data/lib/cosmos/dart/lib/dart_master_query.rb +71 -0
  48. data/lib/cosmos/dart/lib/dart_reducer_worker_thread.rb +165 -134
  49. data/lib/cosmos/dart/processes/dart.rb +4 -2
  50. data/lib/cosmos/dart/processes/dart_decom_server.rb +2 -2
  51. data/lib/cosmos/dart/processes/dart_ingester.rb +38 -1
  52. data/lib/cosmos/dart/processes/dart_master.rb +44 -0
  53. data/lib/cosmos/dart/processes/dart_util.rb +115 -0
  54. data/lib/cosmos/gui/widgets/dart_meta_frame.rb +21 -2
  55. data/lib/cosmos/interfaces/protocols/length_protocol.rb +5 -0
  56. data/lib/cosmos/io/json_drb.rb +3 -3
  57. data/lib/cosmos/io/posix_serial_driver.rb +1 -1
  58. data/lib/cosmos/io/win32_serial_driver.rb +23 -2
  59. data/lib/cosmos/packet_logs/packet_log_reader.rb +2 -2
  60. data/lib/cosmos/packets/packet.rb +1 -1
  61. data/lib/cosmos/packets/packet_config.rb +26 -8
  62. data/lib/cosmos/packets/structure.rb +17 -0
  63. data/lib/cosmos/packets/structure_item.rb +5 -1
  64. data/lib/cosmos/packets/telemetry.rb +7 -1
  65. data/lib/cosmos/system/system.rb +115 -48
  66. data/lib/cosmos/tools/cmd_sender/cmd_params.rb +360 -0
  67. data/lib/cosmos/tools/cmd_sender/cmd_sender.rb +23 -319
  68. data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +14 -17
  69. data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +43 -331
  70. data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +16 -11
  71. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +1 -0
  72. data/lib/cosmos/tools/config_editor/config_editor.rb +33 -2
  73. data/lib/cosmos/tools/config_editor/config_editor_frame.rb +8 -9
  74. data/lib/cosmos/tools/config_editor/system_config_dialog.rb +158 -0
  75. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +2 -2
  76. data/lib/cosmos/tools/test_runner/test.rb +5 -2
  77. data/lib/cosmos/tools/test_runner/test_runner.rb +2 -2
  78. data/lib/cosmos/tools/tlm_extractor/tlm_extractor_processor.rb +17 -13
  79. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_dart_thread.rb +20 -16
  80. data/lib/cosmos/tools/tlm_grapher/tlm_grapher.rb +18 -11
  81. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +16 -5
  82. data/lib/cosmos/utilities/ruby_lex_utils.rb +34 -30
  83. data/lib/cosmos/version.rb +4 -4
  84. data/lib/cosmos/win32/excel.rb +23 -17
  85. data/run_gui_tests.bat +1 -0
  86. data/spec/core_ext/socket_spec.rb +1 -1
  87. data/spec/install/yaml_docs_spec.rb +26 -6
  88. data/spec/interfaces/protocols/length_protocol_spec.rb +39 -0
  89. data/spec/io/json_drb_spec.rb +14 -0
  90. data/spec/io/win32_serial_driver_spec.rb +16 -2
  91. data/spec/packet_logs/packet_log_reader_spec.rb +2 -2
  92. data/spec/packets/structure_spec.rb +52 -2
  93. data/spec/packets/telemetry_spec.rb +29 -1
  94. data/spec/system/system_spec.rb +2 -2
  95. data/spec/utilities/message_log_spec.rb +6 -3
  96. data/tasks/gemfile_stats.rake +22 -13
  97. metadata +17 -7
  98. data/lib/cosmos/dart/Gemfile +0 -69
@@ -303,6 +303,23 @@ module Cosmos
303
303
  end
304
304
  end
305
305
 
306
+ # @param name [String] Name of the item to delete in the items Hash
307
+ def delete_item(name)
308
+ item = @items[name.upcase]
309
+ raise ArgumentError, "Unknown item: #{name}" unless item
310
+
311
+ # Find the item to delete in the sorted_items array
312
+ item_index = nil
313
+ @sorted_items.each_with_index do |sorted_item, index|
314
+ if sorted_item.name == item.name
315
+ item_index = index
316
+ break
317
+ end
318
+ end
319
+ @sorted_items.delete_at(item_index)
320
+ @items.delete(name.upcase)
321
+ end
322
+
306
323
  # Write a value to the buffer based on the item definition
307
324
  #
308
325
  # @param item [StructureItem] Instance of StructureItem or one of its subclasses
@@ -11,7 +11,6 @@
11
11
  require 'cosmos/ext/packet' if RUBY_ENGINE == 'ruby' and !ENV['COSMOS_NO_EXT']
12
12
 
13
13
  module Cosmos
14
-
15
14
  # Maintains knowledge of an item in a Structure. Multiple StructureItems
16
15
  # compose a Structure.
17
16
  class StructureItem
@@ -59,6 +58,9 @@ module Cosmos
59
58
  # @return [Symbol] {BinaryAccessor::OVERFLOW_TYPES}
60
59
  attr_reader :overflow
61
60
 
61
+ # @return [Boolean] Whether this structure item can overlap another item in the same packet
62
+ attr_accessor :overlap
63
+
62
64
  # A large buffer size in bits (1 Megabyte)
63
65
  LARGE_BUFFER_SIZE_BITS = 1024 * 1024 * 8
64
66
 
@@ -73,6 +75,7 @@ module Cosmos
73
75
  # @param endianness [Symbol] {BinaryAccessor::ENDIANNESS}
74
76
  # @param array_size [Integer, nil] Size of the array item in bits. For
75
77
  # example, if the bit_size is 8, an array_size of 16 holds two values.
78
+ # @param overflow [Symbol] {BinaryAccessor::OVERFLOW_TYPES}
76
79
  def initialize(name, bit_offset, bit_size, data_type, endianness, array_size = nil, overflow = :ERROR)
77
80
  @structure_item_constructed = false
78
81
  # Assignment order matters due to verifications!
@@ -83,6 +86,7 @@ module Cosmos
83
86
  self.bit_size = bit_size
84
87
  self.array_size = array_size
85
88
  self.overflow = overflow
89
+ self.overlap = false
86
90
  @create_index = @@create_index
87
91
  @@create_index += 1
88
92
  @structure_item_constructed = true
@@ -437,7 +437,13 @@ module Cosmos
437
437
  splash.progress = index / total
438
438
  end
439
439
 
440
- ignored_items = System.targets[target_name].ignored_items
440
+ # Note: System only has declared target structures but telemetry may have more
441
+ system_target = System.targets[target_name]
442
+ if system_target
443
+ ignored_items = system_target.ignored_items
444
+ else
445
+ ignored_items = []
446
+ end
441
447
 
442
448
  packets(target_name).each do |packet_name, packet|
443
449
  # We don't audit against hidden or disabled packets
@@ -20,6 +20,7 @@ require 'drb/acl'
20
20
  require 'zip'
21
21
  require 'zip/filesystem'
22
22
  require 'bundler'
23
+ require 'thread'
23
24
 
24
25
  module Cosmos
25
26
  # System is the primary entry point into the COSMOS framework. It captures
@@ -77,9 +78,9 @@ module Cosmos
77
78
  instance_attr_reader :hashing_algorithm
78
79
 
79
80
  # Known COSMOS ports
80
- KNOWN_PORTS = ['CTS_API', 'TLMVIEWER_API', 'CTS_PREIDENTIFIED', 'CTS_CMD_ROUTER', 'REPLAY_API', 'REPLAY_PREIDENTIFIED', 'REPLAY_CMD_ROUTER', 'DART_STREAM', 'DART_DECOM']
81
+ KNOWN_PORTS = ['CTS_API', 'TLMVIEWER_API', 'CTS_PREIDENTIFIED', 'CTS_CMD_ROUTER', 'REPLAY_API', 'REPLAY_PREIDENTIFIED', 'REPLAY_CMD_ROUTER', 'DART_STREAM', 'DART_DECOM', 'DART_MASTER']
81
82
  # Known COSMOS hosts
82
- KNOWN_HOSTS = ['CTS_API', 'TLMVIEWER_API', 'CTS_PREIDENTIFIED', 'CTS_CMD_ROUTER', 'REPLAY_API', 'REPLAY_PREIDENTIFIED', 'REPLAY_CMD_ROUTER', 'DART_STREAM', 'DART_DECOM']
83
+ KNOWN_HOSTS = ['CTS_API', 'TLMVIEWER_API', 'CTS_PREIDENTIFIED', 'CTS_CMD_ROUTER', 'REPLAY_API', 'REPLAY_PREIDENTIFIED', 'REPLAY_CMD_ROUTER', 'DART_STREAM', 'DART_DECOM', 'DART_MASTER']
83
84
  # Known COSMOS paths
84
85
  KNOWN_PATHS = ['LOGS', 'TMP', 'SAVED_CONFIG', 'TABLES', 'HANDBOOKS', 'PROCEDURES', 'SEQUENCES', 'DART_DATA', 'DART_LOGS']
85
86
  # Supported hashing algorithms
@@ -195,6 +196,7 @@ module Cosmos
195
196
  @targets = {}
196
197
  # Set config to nil so things will lazy load later
197
198
  @config = nil
199
+ @meta_init_filename = nil
198
200
  @use_utc = false
199
201
  acl_list = []
200
202
  all_allowed = false
@@ -207,7 +209,7 @@ module Cosmos
207
209
  # First pass - Everything except targets
208
210
  parser.parse_file(filename) do |keyword, parameters|
209
211
  case keyword
210
- when 'AUTO_DECLARE_TARGETS', 'DECLARE_TARGET', 'DECLARE_GEM_TARGET'
212
+ when 'AUTO_DECLARE_TARGETS', 'DECLARE_TARGET', 'DECLARE_GEM_TARGET', 'DECLARE_GEM_MULTI_TARGET'
211
213
  # Will be handled by second pass
212
214
 
213
215
  when 'PORT'
@@ -458,6 +460,19 @@ module Cosmos
458
460
  target = Target.new(target_name, substitute_name, configuration_directory, ConfigParser.handle_nil(parameters[2]), gem_dir)
459
461
  @targets[target.name] = target
460
462
 
463
+ when 'DECLARE_GEM_MULTI_TARGET'
464
+ usage = "#{keyword} <GEM NAME> <TARGET NAME> <SUBSTITUTE TARGET NAME (Optional)> <TARGET FILENAME (Optional - defaults to target.txt)>"
465
+ parser.verify_num_parameters(2, 4, usage)
466
+
467
+ target_name = parameters[1].to_s.upcase
468
+ substitute_name = nil
469
+ substitute_name = ConfigParser.handle_nil(parameters[2])
470
+ substitute_name.to_s.upcase if substitute_name
471
+ gem_dir = Gem::Specification.find_by_name(parameters[0]).gem_dir
472
+ gem_dir = File.join(gem_dir, target_name)
473
+ target = Target.new(target_name, substitute_name, configuration_directory, ConfigParser.handle_nil(parameters[3]), gem_dir)
474
+ @targets[target.name] = target
475
+
461
476
  end # case keyword
462
477
  end # parser.parse_file
463
478
 
@@ -476,48 +491,65 @@ module Cosmos
476
491
  # configuration. Pass nil to load the default configuration.
477
492
  # @return [String, Exception/nil] The actual configuration loaded
478
493
  def load_configuration(name = nil)
479
- unless @config
480
- # Ensure packets have been lazy loaded
481
- System.commands
482
- end
494
+ # Ensure packets have been lazy loaded
495
+ load_packets() unless @config
483
496
 
484
- if name && @config
485
- # Make sure they're requesting something other than the current
486
- # configuration.
487
- if name != @config.name
488
- # If they want the initial configuration we can just swap out the
489
- # current configuration without doing any file processing
490
- if name == @initial_config.name
491
- update_config(@initial_config)
492
- else
493
- # Look for the requested configuration in the saved configurations
494
- configuration = find_configuration(name)
495
- if configuration
496
- # We found the configuration requested. Reprocess the system.txt
497
- # and reload the packets
498
- begin
499
- unless File.directory?(configuration)
500
- # Zip file configuration so unzip and reset configuration path
501
- configuration = unzip(configuration)
497
+ @@instance_mutex.synchronize do
498
+ if @config_blacklist[name]
499
+ Logger.warn "Ignoring failed config #{name}"
500
+ update_config(@initial_config)
501
+ return @config.name, RuntimeError.new("Ignoring failed config #{name}")
502
+ end
503
+
504
+ if name && @config
505
+ # Make sure they're requesting something other than the current
506
+ # configuration.
507
+ if name != @config.name
508
+ # If they want the initial configuration we can just swap out the
509
+ # current configuration without doing any file processing
510
+ if name == @initial_config.name
511
+ Logger.info "Switching to initial configuration: #{name}"
512
+ update_config(@initial_config)
513
+ else
514
+ # Look for the requested configuration in the saved configurations
515
+ configuration = find_configuration(name)
516
+ if configuration
517
+ # We found the configuration requested. Reprocess the system.txt
518
+ # and reload the packets
519
+ begin
520
+ unless File.directory?(configuration)
521
+ # Zip file configuration so unzip and reset configuration path
522
+ configuration = unzip(configuration)
523
+ end
524
+
525
+ Logger.info "Switching to configuration: #{name}"
526
+ process_file(File.join(configuration, 'system.txt'), configuration)
527
+ load_packets(name, false)
528
+ rescue Exception => error
529
+ # Failed to load - Restore initial
530
+ @config_blacklist[name] = true # Prevent wasting time trying to load the bad configuration again
531
+ Logger.error "Problem loading configuration from #{configuration}: #{error.class}:#{error.message}\n#{error.backtrace.join("\n")}\n"
532
+ Logger.info "Switching to initial configuration: #{@initial_config.name}"
533
+ update_config(@initial_config)
534
+ return @config.name, error
502
535
  end
503
- process_file(File.join(configuration, 'system.txt'), configuration)
504
- load_packets(name)
505
- rescue Exception => error
506
- # Failed to load - Restore initial
536
+ else
537
+ # We couldn't find the configuration request. Reload the
538
+ # initial configuration
539
+ Logger.error "Unable to find configuration: #{name}"
540
+ Logger.info "Switching to initial configuration: #{@initial_config.name}"
507
541
  update_config(@initial_config)
508
- return @config.name, error
542
+ return @config.name, RuntimeError.new("Unable to find configuration: #{name}")
509
543
  end
510
- else
511
- # We couldn't find the configuration request. Reload the
512
- # initial configuration
513
- update_config(@initial_config)
514
544
  end
515
545
  end
546
+ else
547
+ Logger.info "Switching to initial configuration: #{@initial_config.name}"
548
+ update_config(@initial_config)
516
549
  end
517
- else
518
- update_config(@initial_config)
550
+
551
+ return @config.name, nil
519
552
  end
520
- return @config.name, nil
521
553
  end
522
554
 
523
555
  # (see #load_configuration)
@@ -530,7 +562,6 @@ module Cosmos
530
562
  # @param filename [String] Path to system.txt config file to process. Defaults to config/system/system.txt
531
563
  def reset_variables(filename = nil)
532
564
  @targets = {}
533
- @targets['UNKNOWN'] = Target.new('UNKNOWN')
534
565
  @config = nil
535
566
  @commands = nil
536
567
  @telemetry = nil
@@ -557,8 +588,10 @@ module Cosmos
557
588
  @ports['REPLAY_API'] = 7877
558
589
  @ports['REPLAY_PREIDENTIFIED'] = 7879
559
590
  @ports['REPLAY_CMD_ROUTER'] = 7880
560
- @ports['DART_DECOM'] = 8777
561
- @ports['DART_STREAM'] = 8779
591
+
592
+ @ports['DART_STREAM'] = 8777
593
+ @ports['DART_DECOM'] = 8779
594
+ @ports['DART_MASTER'] = 8780
562
595
 
563
596
  @listen_hosts = {}
564
597
  @listen_hosts['CTS_API'] = '127.0.0.1'
@@ -572,6 +605,7 @@ module Cosmos
572
605
  @listen_hosts['REPLAY_CMD_ROUTER'] = '0.0.0.0'
573
606
  @listen_hosts['DART_STREAM'] = '0.0.0.0'
574
607
  @listen_hosts['DART_DECOM'] = '0.0.0.0'
608
+ @listen_hosts['DART_MASTER'] = '0.0.0.0'
575
609
 
576
610
  @connect_hosts = {}
577
611
  @connect_hosts['CTS_API'] = '127.0.0.1'
@@ -583,6 +617,7 @@ module Cosmos
583
617
  @connect_hosts['REPLAY_CMD_ROUTER'] = '127.0.0.1'
584
618
  @connect_hosts['DART_STREAM'] = '127.0.0.1'
585
619
  @connect_hosts['DART_DECOM'] = '127.0.0.1'
620
+ @connect_hosts['DART_MASTER'] = '127.0.0.1'
586
621
 
587
622
  @paths = {}
588
623
  @paths['LOGS'] = File.join(USERPATH, 'outputs', 'logs')
@@ -611,6 +646,7 @@ module Cosmos
611
646
 
612
647
  @initial_filename = filename
613
648
  @initial_config = nil
649
+ @config_blacklist = {}
614
650
  end
615
651
 
616
652
  # Reset variables and load packets
@@ -689,11 +725,31 @@ module Cosmos
689
725
  Bundler.load.specs.each do |spec|
690
726
  spec_name_split = spec.name.split('-')
691
727
  if spec_name_split.length > 1 && (spec_name_split[0] == 'cosmos')
692
- # Filter to just targets and not tools and other extensions
693
- if File.exist?(File.join(spec.gem_dir, 'cmd_tlm'))
694
- target_name = spec_name_split[1..-1].join('-').to_s.upcase
695
- target = Target.new(target_name, nil, nil, nil, spec.gem_dir)
696
- @targets[target.name] = target
728
+ # search for multiple targets packaged in a single gem
729
+ dirs = []
730
+ Dir.foreach(spec.gem_dir) { |dir_filename| dirs << dir_filename }
731
+ dirs.sort!
732
+ dirs.each do |dir_filename|
733
+ if dir_filename == "."
734
+ # check the base directory
735
+ curr_dir = spec.gem_dir
736
+ target_name = spec_name_split[1..-1].join('-').to_s.upcase
737
+ else
738
+ #check for targets in other directories 1 level deep
739
+ next if dir_filename[0] == '.' #skip dot directories and ".."
740
+ next if dir_filename != dir_filename.upcase #skip non uppercase directories
741
+ curr_dir = File.join(spec.gem_dir, dir_filename)
742
+ target_name = dir_filename
743
+ end
744
+ # check for the cmd_tlm directory - if it has it, then we have found a target
745
+ if File.directory?(File.join(curr_dir,'cmd_tlm'))
746
+ # If any of the targets original directory name matches the
747
+ # current directory then it must have been already processed by
748
+ # DECLARE_TARGET so we skip it.
749
+ next if @targets.select {|name, target| target.original_name == target_name }.length > 0
750
+ target = Target.new(target_name,nil, nil, nil, spec.gem_dir)
751
+ @targets[target.name] = target
752
+ end
697
753
  end
698
754
  end
699
755
  end
@@ -722,9 +778,10 @@ module Cosmos
722
778
  configuration = find_configuration(@config.name)
723
779
  configuration = File.join(@paths['SAVED_CONFIG'], File.build_timestamped_filename([@config.name], '.zip')) unless configuration
724
780
  unless File.exist?(configuration)
781
+ configuration_tmp = File.join(@paths['SAVED_CONFIG'], File.build_timestamped_filename(['tmp_' + @config.name], '.zip.tmp'))
725
782
  begin
726
783
  Zip.continue_on_exists_proc = true
727
- Zip::File.open(configuration, Zip::File::CREATE) do |zipfile|
784
+ Zip::File.open(configuration_tmp, Zip::File::CREATE) do |zipfile|
728
785
  zip_file_path = File.basename(configuration, ".zip")
729
786
  zipfile.mkdir zip_file_path
730
787
 
@@ -756,6 +813,7 @@ module Cosmos
756
813
  end
757
814
  end
758
815
  end
816
+ File.rename(configuration_tmp, configuration)
759
817
  File.chmod(0444, configuration) # Mark readonly
760
818
  rescue Exception => error
761
819
  Logger.error "Problem saving configuration to #{configuration}: #{error.class}:#{error.message}\n#{error.backtrace.join("\n")}\n"
@@ -764,7 +822,7 @@ module Cosmos
764
822
  end
765
823
  end
766
824
 
767
- def load_packets(configuration_name = nil)
825
+ def load_packets_internal(configuration_name = nil)
768
826
  # Determine hashing over all targets cmd_tlm files
769
827
  cmd_tlm_files = []
770
828
  additional_data = ''
@@ -785,7 +843,6 @@ module Cosmos
785
843
  # Only use at most, 32 characters of the hex
786
844
  hash_string = hash_string[-32..-1] if hash_string.length >= 32
787
845
 
788
-
789
846
  # Build filename for marshal file
790
847
  marshal_filename = File.join(@paths['TMP'], 'marshal_' << hash_string << '.bin')
791
848
 
@@ -831,6 +888,16 @@ module Cosmos
831
888
  save_configuration()
832
889
  end
833
890
 
891
+ def load_packets(configuration_name = nil, take_mutex = true)
892
+ if take_mutex
893
+ @@instance_mutex.synchronize do
894
+ load_packets_internal(configuration_name)
895
+ end
896
+ else
897
+ load_packets_internal(configuration_name)
898
+ end
899
+ end
900
+
834
901
  def setup_system_meta
835
902
  # Ensure SYSTEM META is defined and defined correctly
836
903
  begin
@@ -0,0 +1,360 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2014 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+
11
+ require 'cosmos'
12
+ Cosmos.catch_fatal_exception do
13
+ require 'cosmos/tools/cmd_sender/cmd_param_table_item_delegate'
14
+ require 'cosmos/gui/dialogs/cmd_details_dialog'
15
+ end
16
+
17
+ module Cosmos
18
+ # Builds a command parameter widget for use in Command Sender and Command Sequence.
19
+ # The main method is update_cmd_params which builds all the widgets. It can take
20
+ # existing values for use in populating the command parameter widgets.
21
+ class CmdParams < Qt::Widget
22
+ # Class instance variables which apply to all command parameters
23
+ @states_in_hex = false
24
+ @show_ignored = false
25
+ class << self
26
+ attr_accessor :states_in_hex, :show_ignored
27
+ end
28
+ MANUALLY = CmdParamTableItemDelegate::MANUALLY
29
+ # Emit the modified signal to allow changes to propagate upwards
30
+ signals 'modified()'
31
+
32
+ def initialize
33
+ super()
34
+ @param_widgets = []
35
+ @table = nil
36
+ @packet = nil
37
+ @file_dir = System.paths['LOGS']
38
+ end
39
+
40
+ # Changes the display of items with states to hex if checked is true.
41
+ # Otherwise state values are displayed as decimal.
42
+ # @param checked [Boolean] Whether to display state values in hex
43
+ def states_in_hex(checked)
44
+ CmdParams.states_in_hex = checked
45
+ @param_widgets.each do |_, _, state_value_item|
46
+ next unless state_value_item
47
+ text = state_value_item.text
48
+ quotes_removed = text.remove_quotes
49
+ if text == quotes_removed
50
+ if checked
51
+ if text.is_int?
52
+ @table.blockSignals(true)
53
+ state_value_item.text = sprintf("0x%X", text.to_i)
54
+ @table.blockSignals(false)
55
+ end
56
+ else
57
+ if text.is_hex?
58
+ @table.blockSignals(true)
59
+ state_value_item.text = Integer(text).to_s
60
+ @table.blockSignals(false)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # @return [Hash] Hash keyed by parameter name with String formatted value
68
+ def params_text(raw = false)
69
+ params = {}
70
+ Qt.execute_in_main_thread do
71
+ @param_widgets.each do |packet_item, value_item, state_value_item|
72
+ text = value_item.text
73
+ text = state_value_item.text if state_value_item && (text == MANUALLY or raw)
74
+ quotes_removed = text.remove_quotes
75
+ if text == quotes_removed
76
+ if (packet_item.data_type == :STRING or packet_item.data_type == :BLOCK) and text.upcase.start_with?("0X")
77
+ params[packet_item.name] = text.hex_to_byte_string
78
+ else
79
+ params[packet_item.name] = text.convert_to_value
80
+ end
81
+ else
82
+ params[packet_item.name] = quotes_removed
83
+ end
84
+ Kernel.raise "#{packet_item.name} is required." if quotes_removed == '' && packet_item.required
85
+ end
86
+ end
87
+ params
88
+ end
89
+
90
+ # Primary method which builds the command parameter table. The passed in command packet
91
+ # is used to get all the command parameters to build. Existing parameters can be passed
92
+ # in to set the initial values for all command parameters. You can also specify whether
93
+ # to display the ignored parameters when building the command parameter table. Note that
94
+ # each time this method is called the TableWidget is disposed and rebuilt.
95
+ #
96
+ # @param packet [Packet] The command packet to build a parameter list for
97
+ # @param existing [Hash] Hash keyed by parameter name with text values.
98
+ # These values will be used as the defaults for all command parameters.
99
+ # @param show_ignored [Boolean] Whether to display the ignored
100
+ # parameters. Pass nil (the default) to keep the existing setting.
101
+ def update_cmd_params(packet, existing: nil, show_ignored: nil)
102
+ @packet = packet
103
+ old_params = {}
104
+ old_params = get_params(show_ignored) if !show_ignored.nil?
105
+ old_params = set_existing(packet, existing) if existing
106
+
107
+ # Determine which items to display
108
+ target = System.targets[packet.target_name]
109
+ packet_items = packet.sorted_items
110
+ shown_packet_items = []
111
+ packet_items.each do |packet_item|
112
+ next if target && target.ignored_parameters.include?(packet_item.name) && !CmdParams.show_ignored
113
+ shown_packet_items << packet_item
114
+ end
115
+
116
+ # Destroy the old table widget and parameters
117
+ @table.dispose if @table
118
+ @table = nil
119
+ @param_widgets = []
120
+ row = 0
121
+ shown_packet_items.each do |packet_item|
122
+ value_item = nil
123
+ state_value_item = nil
124
+ @table = create_table(shown_packet_items.length) unless @table
125
+
126
+ # Parameter Name
127
+ item = Qt::TableWidgetItem.new("#{packet_item.name}:")
128
+ item.setTextAlignment(Qt::AlignRight | Qt::AlignVCenter)
129
+ item.setFlags(Qt::NoItemFlags | Qt::ItemIsSelectable | Qt::ItemIsEnabled)
130
+ @table.setItem(row, 0, item)
131
+
132
+ # Parameter Value
133
+ if packet_item.states
134
+ value_item, state_value_item = create_state_item(packet_item, old_params, row)
135
+ else
136
+ value_item = create_item(packet_item, old_params, row)
137
+ end
138
+
139
+ # Units
140
+ item = Qt::TableWidgetItem.new(packet_item.units.to_s)
141
+ item.setTextAlignment(Qt::AlignRight | Qt::AlignVCenter)
142
+ item.setFlags(Qt::NoItemFlags | Qt::ItemIsSelectable | Qt::ItemIsEnabled)
143
+ @table.setItem(row, 3, item)
144
+
145
+ # Description
146
+ item = Qt::TableWidgetItem.new(packet_item.description.to_s)
147
+ item.setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter)
148
+ item.setFlags(Qt::NoItemFlags | Qt::ItemIsSelectable | Qt::ItemIsEnabled)
149
+ @table.setItem(row, 4, item)
150
+
151
+ @param_widgets << [packet_item, value_item, state_value_item]
152
+ row += 1
153
+ end
154
+
155
+ connect_table_item_changed() if @table
156
+ @table
157
+ end
158
+
159
+ # If the user right clicks over a table item, this method displays a context
160
+ # menu with various options.
161
+ # @param point [Qt::Point] Point to display the context menu
162
+ def context_menu(point)
163
+ item = @table.itemAt(point)
164
+ if item
165
+ target_name = @packet.target_name
166
+ packet_name = @packet.packet_name
167
+ item_name = @table.item(item.row, 0).text[0..-2] # Remove :
168
+ if target_name.length > 0 && packet_name.length > 0 && item_name.length > 0
169
+ menu = Qt::Menu.new()
170
+
171
+ details_action = Qt::Action.new("Details #{target_name} #{packet_name} #{item_name}", self)
172
+ details_action.statusTip = "Popup details about #{target_name} #{packet_name} #{item_name}"
173
+ details_action.connect(SIGNAL('triggered()')) do
174
+ CmdDetailsDialog.new(nil, target_name, packet_name, item_name)
175
+ end
176
+ menu.addAction(details_action)
177
+
178
+ file_chooser_action = Qt::Action.new("Insert Filename", self)
179
+ file_chooser_action.statusTip = "Select a file and place its name into this parameter"
180
+ file_chooser_action.connect(SIGNAL('triggered()')) do
181
+ filename = Qt::FileDialog::getOpenFileName(self, "Insert Filename:", @file_dir, "All Files (*)")
182
+ if filename && !filename.empty?
183
+ @file_dir = File.dirname(filename)
184
+ _, value_item, state_value_item = @param_widgets[item.row]
185
+ if state_value_item
186
+ state_value_item.setText(filename)
187
+ elsif value_item
188
+ value_item.setText(filename)
189
+ end
190
+ end
191
+ end
192
+ menu.addAction(file_chooser_action)
193
+
194
+ menu.exec(@table.mapToGlobal(point))
195
+ menu.dispose
196
+ end
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def get_params(show_ignored)
203
+ params = {}
204
+ CmdParams.show_ignored = show_ignored
205
+ # Save parameter values
206
+ @param_widgets.each do |packet_item, value_item, state_value_item|
207
+ text = value_item.text
208
+ if state_value_item
209
+ params[packet_item.name] = [text, state_value_item.text]
210
+ else
211
+ params[packet_item.name] = text
212
+ end
213
+ end
214
+ params
215
+ end
216
+
217
+ def set_existing(packet, existing)
218
+ params = {}
219
+ existing.each do |param_name, param_value|
220
+ packet_item = packet.items[param_name]
221
+ if packet_item.states
222
+ state_value = packet_item.states[param_value]
223
+ unless state_value # If we couldn't lookup the value by the name it's manual
224
+ state_value = param_value
225
+ param_value = MANUALLY
226
+ end
227
+ params[param_name] = [param_value.to_s, state_value.to_s]
228
+ else
229
+ params[param_name] = param_value.to_s
230
+ end
231
+ end
232
+ params
233
+ end
234
+
235
+ def create_table(length)
236
+ table = Qt::TableWidget.new()
237
+ table.setSizePolicy(Qt::SizePolicy::Expanding, Qt::SizePolicy::Expanding)
238
+ table.setWordWrap(true)
239
+ table.setRowCount(length)
240
+ table.setColumnCount(5)
241
+ table.setHorizontalHeaderLabels(['Name', ' Value or State ', ' ', 'Units', 'Description'])
242
+ table.horizontalHeader.setStretchLastSection(true)
243
+ table.verticalHeader.setVisible(false)
244
+ table.setItemDelegate(CmdParamTableItemDelegate.new(table, @param_widgets, @production))
245
+ table.setContextMenuPolicy(Qt::CustomContextMenu)
246
+ table.verticalHeader.setResizeMode(Qt::HeaderView::ResizeToContents)
247
+ table.setEditTriggers(Qt::AbstractItemView::DoubleClicked | Qt::AbstractItemView::SelectedClicked | Qt::AbstractItemView::AnyKeyPressed)
248
+ table.setSelectionMode(Qt::AbstractItemView::NoSelection)
249
+ table.connect(SIGNAL('customContextMenuRequested(const QPoint&)')) {|point| context_menu(point) }
250
+ table.connect(SIGNAL('itemClicked(QTableWidgetItem*)')) do |item|
251
+ table.editItem(item) if (item.flags & Qt::ItemIsEditable) != 0
252
+ end
253
+ return table
254
+ end
255
+
256
+ def create_state_item(packet_item, old_params, row)
257
+ default_state = packet_item.states.key(packet_item.default)
258
+ if old_params[packet_item.name]
259
+ value_item = Qt::TableWidgetItem.new(old_params[packet_item.name][0])
260
+ else
261
+ if default_state
262
+ value_item = Qt::TableWidgetItem.new(default_state.to_s)
263
+ elsif @production
264
+ value_item = Qt::TableWidgetItem.new(packet_item.states.keys[0])
265
+ else
266
+ value_item = Qt::TableWidgetItem.new(MANUALLY)
267
+ end
268
+ end
269
+ value_item.setTextAlignment(Qt::AlignRight | Qt::AlignVCenter)
270
+ value_item.setFlags(Qt::NoItemFlags | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable)
271
+ @table.setItem(row, 1, value_item)
272
+
273
+ state_value = packet_item.default.to_s
274
+ if old_params[packet_item.name]
275
+ state_value = old_params[packet_item.name][1]
276
+ end
277
+ is_integer = Integer(state_value) rescue false
278
+ if CmdParams.states_in_hex && is_integer
279
+ state_value_item = Qt::TableWidgetItem.new(sprintf("0x%X", state_value))
280
+ else
281
+ if state_value.is_printable?
282
+ state_value_item = Qt::TableWidgetItem.new(state_value)
283
+ else
284
+ state_value_item = Qt::TableWidgetItem.new("0x" + state_value.simple_formatted)
285
+ end
286
+ end
287
+ state_value_item.setTextAlignment(Qt::AlignRight | Qt::AlignVCenter)
288
+ if @production
289
+ state_value_item.setFlags(Qt::NoItemFlags)
290
+ else
291
+ state_value_item.setFlags(Qt::NoItemFlags | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable)
292
+ end
293
+ @table.setItem(row, 2, state_value_item)
294
+
295
+ # If the parameter is required clear the combobox and
296
+ # clear the value field so they have to choose something
297
+ if packet_item.required && !old_params[packet_item.name]
298
+ value_item.setText('')
299
+ state_value_item.setText('')
300
+ end
301
+ return [value_item, state_value_item]
302
+ end
303
+
304
+ def create_item(packet_item, old_params, row)
305
+ if old_params[packet_item.name]
306
+ value_text = old_params[packet_item.name]
307
+ elsif packet_item.required
308
+ value_text = ''
309
+ else
310
+ if packet_item.format_string
311
+ begin
312
+ value_text = sprintf(packet_item.format_string, packet_item.default)
313
+ rescue
314
+ # Oh well - Don't use the format string
315
+ value_text = packet_item.default.to_s
316
+ end
317
+ else
318
+ value_text = packet_item.default.to_s
319
+ end
320
+ end
321
+ if !value_text.is_printable?
322
+ value_text = "0x" + value_text.simple_formatted
323
+ # Add quotes around STRING or BLOCK defaults so they are interpreted correctly
324
+ elsif (packet_item.data_type == :STRING or packet_item.data_type == :BLOCK)
325
+ value_text = "'#{packet_item.default}'"
326
+ end
327
+ value_item = Qt::TableWidgetItem.new(value_text)
328
+ value_item.setTextAlignment(Qt::AlignRight | Qt::AlignVCenter)
329
+ value_item.setFlags(Qt::NoItemFlags | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable)
330
+ @table.setItem(row, 1, value_item)
331
+ @table.setSpan(row, 1, 1, 2)
332
+ return value_item
333
+ end
334
+
335
+ def connect_table_item_changed
336
+ @table.connect(SIGNAL('itemChanged(QTableWidgetItem*)')) do |item|
337
+ packet_item, value_item, state_value_item = @param_widgets[item.row]
338
+ if item.column == 1
339
+ if packet_item.states
340
+ value = packet_item.states[value_item.text]
341
+ @table.blockSignals(true)
342
+ if CmdParams.states_in_hex && value.kind_of?(Integer)
343
+ state_value_item.setText(sprintf("0x%X", value))
344
+ else
345
+ state_value_item.setText(value.to_s)
346
+ end
347
+ @table.blockSignals(false)
348
+ end
349
+ elsif item.column == 2
350
+ @table.blockSignals(true)
351
+ @table.item(item.row, 1).setText(MANUALLY)
352
+ @table.blockSignals(false)
353
+ end
354
+ emit modified()
355
+ end
356
+ @table.resizeColumnsToContents()
357
+ @table.resizeRowsToContents()
358
+ end
359
+ end
360
+ end