cosmos 4.4.0 → 4.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.dockerignore +2 -0
- data/.gitignore +1 -0
- data/.travis.yml +6 -6
- data/Dockerfile +65 -0
- data/Manifest.txt +12 -2
- data/README.md +5 -0
- data/Rakefile +52 -0
- data/appveyor.yml +18 -8
- data/autohotkey/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
- data/autohotkey/lib/cmd_sequence_exporter.rb +52 -0
- data/autohotkey/procedures/collect.rb +2 -2
- data/autohotkey/procedures/collect_util.rb +1 -1
- data/autohotkey/procedures/script_test.rb +1 -1
- data/autohotkey/tools/CmdSenderAHK2 +18 -0
- data/autohotkey/tools/cmd_sender.ahk +34 -6
- data/autohotkey/tools/cmd_sender2.ahk +4 -0
- data/autohotkey/tools/cmd_sequence.ahk +21 -8
- data/autohotkey/tools/config_editor.ahk +4 -4
- data/bin/cstol_converter +1 -1
- data/cosmos.gemspec +1 -1
- data/data/config/command_modifiers.yaml +16 -1
- data/data/config/param_item_modifiers.yaml +5 -0
- data/data/config/system.yaml +31 -1
- data/data/config/telemetry_modifiers.yaml +16 -1
- data/data/crc.txt +415 -410
- data/demo/config/dart/Gemfile +1 -6
- data/demo/config/data/crc.txt +244 -243
- data/demo/config/system/system.txt +3 -0
- data/demo/config/system/system2.txt +3 -0
- data/demo/config/system/system_alt_ports.txt +3 -0
- data/demo/config/targets/INST/cmd_tlm/inst_cmds.txt +3 -3
- data/demo/config/targets/INST/cmd_tlm/inst_tlm.txt +4 -0
- data/demo/config/targets/INST/cmd_tlm/inst_tlm_override.txt +12 -0
- data/demo/config/targets/INST/lib/sim_inst.rb +2 -2
- data/demo/config/targets/INST/target.txt +1 -0
- data/demo/procedures/cosmos_api_test.rb +8 -8
- data/install/config/dart/Gemfile +2 -7
- data/install/config/data/crc.txt +143 -143
- data/install/config/system/system.txt +3 -0
- data/lib/cosmos/dart/config/boot.rb +1 -1
- data/lib/cosmos/dart/config/database.yml +2 -0
- data/lib/cosmos/dart/lib/dart_common.rb +11 -4
- data/lib/cosmos/dart/lib/dart_constants.rb +15 -0
- data/lib/cosmos/dart/lib/dart_decom_query.rb +5 -6
- data/lib/cosmos/dart/lib/dart_decommutator.rb +66 -56
- data/lib/cosmos/dart/lib/dart_master_query.rb +71 -0
- data/lib/cosmos/dart/lib/dart_reducer_worker_thread.rb +165 -134
- data/lib/cosmos/dart/processes/dart.rb +4 -2
- data/lib/cosmos/dart/processes/dart_decom_server.rb +2 -2
- data/lib/cosmos/dart/processes/dart_ingester.rb +38 -1
- data/lib/cosmos/dart/processes/dart_master.rb +44 -0
- data/lib/cosmos/dart/processes/dart_util.rb +115 -0
- data/lib/cosmos/gui/widgets/dart_meta_frame.rb +21 -2
- data/lib/cosmos/interfaces/protocols/length_protocol.rb +5 -0
- data/lib/cosmos/io/json_drb.rb +3 -3
- data/lib/cosmos/io/posix_serial_driver.rb +1 -1
- data/lib/cosmos/io/win32_serial_driver.rb +23 -2
- data/lib/cosmos/packet_logs/packet_log_reader.rb +2 -2
- data/lib/cosmos/packets/packet.rb +1 -1
- data/lib/cosmos/packets/packet_config.rb +26 -8
- data/lib/cosmos/packets/structure.rb +17 -0
- data/lib/cosmos/packets/structure_item.rb +5 -1
- data/lib/cosmos/packets/telemetry.rb +7 -1
- data/lib/cosmos/system/system.rb +115 -48
- data/lib/cosmos/tools/cmd_sender/cmd_params.rb +360 -0
- data/lib/cosmos/tools/cmd_sender/cmd_sender.rb +23 -319
- data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +14 -17
- data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +43 -331
- data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +16 -11
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +1 -0
- data/lib/cosmos/tools/config_editor/config_editor.rb +33 -2
- data/lib/cosmos/tools/config_editor/config_editor_frame.rb +8 -9
- data/lib/cosmos/tools/config_editor/system_config_dialog.rb +158 -0
- data/lib/cosmos/tools/script_runner/script_runner_frame.rb +2 -2
- data/lib/cosmos/tools/test_runner/test.rb +5 -2
- data/lib/cosmos/tools/test_runner/test_runner.rb +2 -2
- data/lib/cosmos/tools/tlm_extractor/tlm_extractor_processor.rb +17 -13
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_dart_thread.rb +20 -16
- data/lib/cosmos/tools/tlm_grapher/tlm_grapher.rb +18 -11
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +16 -5
- data/lib/cosmos/utilities/ruby_lex_utils.rb +34 -30
- data/lib/cosmos/version.rb +4 -4
- data/lib/cosmos/win32/excel.rb +23 -17
- data/run_gui_tests.bat +1 -0
- data/spec/core_ext/socket_spec.rb +1 -1
- data/spec/install/yaml_docs_spec.rb +26 -6
- data/spec/interfaces/protocols/length_protocol_spec.rb +39 -0
- data/spec/io/json_drb_spec.rb +14 -0
- data/spec/io/win32_serial_driver_spec.rb +16 -2
- data/spec/packet_logs/packet_log_reader_spec.rb +2 -2
- data/spec/packets/structure_spec.rb +52 -2
- data/spec/packets/telemetry_spec.rb +29 -1
- data/spec/system/system_spec.rb +2 -2
- data/spec/utilities/message_log_spec.rb +6 -3
- data/tasks/gemfile_stats.rake +22 -13
- metadata +17 -7
- 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
|
-
|
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
|
data/lib/cosmos/system/system.rb
CHANGED
@@ -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
|
-
|
480
|
-
|
481
|
-
System.commands
|
482
|
-
end
|
494
|
+
# Ensure packets have been lazy loaded
|
495
|
+
load_packets() unless @config
|
483
496
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
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
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
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,
|
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
|
-
|
518
|
-
|
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
|
-
|
561
|
-
@ports['DART_STREAM'] =
|
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
|
-
#
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
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(
|
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
|
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
|