cosmos 4.4.0-java → 4.4.1-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 (98) hide show
  1. checksums.yaml +4 -4
  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 +15 -5
  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