lib/cosmos/tools/tlm_extractor/tlm_extractor.rb in cosmos-3.5.0 vs lib/cosmos/tools/tlm_extractor/tlm_extractor.rb in cosmos-3.5.1
- old
+ new
@@ -1,1016 +1,1016 @@
-# encoding: ascii-8bit
-
-# Copyright 2014 Ball Aerospace & Technologies Corp.
-# All Rights Reserved.
-#
-# This program is free software; you can modify and/or redistribute it
-# under the terms of the GNU General Public License
-# as published by the Free Software Foundation; version 3 with
-# attribution addendums as found in the LICENSE.txt
-
-require 'cosmos'
-Cosmos.catch_fatal_exception do
- require 'cosmos/tools/tlm_extractor/tlm_extractor_processor'
- require 'cosmos/tools/tlm_extractor/text_item_chooser'
- require 'cosmos/gui/qt_tool'
- require 'cosmos/gui/choosers/telemetry_chooser'
- require 'cosmos/gui/choosers/float_chooser'
- require 'cosmos/gui/dialogs/splash'
- require 'cosmos/gui/widgets/packet_log_frame'
- require 'cosmos/gui/dialogs/progress_dialog'
- require 'cosmos/gui/widgets/full_text_search_line_edit'
-end
-
-module Cosmos
-
- # TlmExtractor class
- #
- # This class implements the TlmExtractor. This application breaks a binary log of telemetry
- # into a csv type file
- #
- class TlmExtractor < QtTool
- slots 'context_menu(const QPoint&)'
-
- FORMATTING_OPTIONS = %w(CONVERTED RAW FORMATTED WITH_UNITS)
-
- class MyListWidget < Qt::ListWidget
- signals 'enterKeyPressed(int)'
-
- def keyPressEvent(event)
- case event.key
- when Qt::Key_Delete, Qt::Key_Backspace
- remove_selected_items()
- when Qt::Key_Return, Qt::Key_Enter
- emit enterKeyPressed(currentRow())
- end
- super(event)
- end
-
- def remove_selected_items
- indexes = selected_items()
- indexes.reverse_each do |index|
- item = takeItem(index)
- item.dispose if item
- end
- end
-
- # Returns an array with the indexes of the selected items
- def selected_items
- selected = []
- index = 0
- self.each do |list_item|
- selected << index if list_item.selected?
- index += 1
- end
- selected
- end
- end
-
- # Constructor
- def initialize(options)
- super(options) # MUST BE FIRST - All code before super is executed twice in RubyQt Based classes
- Cosmos.load_cosmos_icon("tlm_extractor.png")
-
- # Define instance variables
- @input_filenames = []
- @log_dir = System.paths['LOGS']
- @config_dir = File.join(Cosmos::USERPATH, 'config', 'tools', 'tlm_extractor', '')
- @cancel = false
-
- initialize_actions()
- initialize_menus()
- initialize_central_widget()
- complete_initialize()
-
- # Bring up slash screen for long duration tasks after creation
- Splash.execute(self) do |splash|
- # Configure CosmosConfig to interact with splash screen
- ConfigParser.splash = splash
-
- System.telemetry
- @search_box.completion_list = System.telemetry.all_item_strings(true, splash)
- @tlm_extractor_config = TlmExtractorConfig.new(options.config_file)
- @tlm_extractor_processor = TlmExtractorProcessor.new
- Qt.execute_in_main_thread(true) do
- @telemetry_chooser.update
- sync_config_to_gui()
- end
-
- # Unconfigure CosmosConfig to interact with splash screen
- ConfigParser.splash = nil
- end
- end
-
- def initialize_actions
- super()
-
- # File Menu Actions
- @open_config = Qt::Action.new(tr('Open &Config'), self)
- @open_config.statusTip = tr('Open configuration file')
- @open_config.connect(SIGNAL('triggered()')) { handle_browse_button() }
-
- @save_config = Qt::Action.new(tr('&Save Config'), self)
- @save_config_keyseq = Qt::KeySequence.new(tr('Ctrl+S'))
- @save_config.shortcut = @save_config_keyseq
- @save_config.statusTip = tr('Save current configuration')
- @save_config.connect(SIGNAL('triggered()')) { handle_save_button() }
-
- @file_options = Qt::Action.new(tr('O&ptions'), self)
- @file_options.statusTip = tr('Open the options dialog')
- @file_options.connect(SIGNAL('triggered()')) { handle_options() }
-
- # Mode Menu Actions
- @fill_down_check = Qt::Action.new(tr('&Fill Down'), self)
- @fill_down_check_keyseq = Qt::KeySequence.new(tr('Ctrl+F'))
- @fill_down_check.shortcut = @fill_down_check_keyseq
- @fill_down_check.statusTip = tr('Fill Down')
- @fill_down_check.setCheckable(true)
-
- @matlab_header_check = Qt::Action.new(tr('&Matlab Header'), self)
- @matlab_header_check_keyseq = Qt::KeySequence.new(tr('Ctrl+M'))
- @matlab_header_check.shortcut = @matlab_header_check_keyseq
- @matlab_header_check.statusTip = tr('Add a Matlab header to the output data')
- @matlab_header_check.setCheckable(true)
-
- @share_columns_check = Qt::Action.new(tr('&Share Columns'), self)
- @share_columns_check.statusTip = tr('Share columns for items with the same name')
- @share_columns_check.setCheckable(true)
-
- @unique_only_check = Qt::Action.new(tr('&Unique Only'), self)
- @unique_only_check_keyseq = Qt::KeySequence.new(tr('Ctrl+U'))
- @unique_only_check.shortcut = @unique_only_check_keyseq
- @unique_only_check.statusTip = tr('Only output rows where data has changed')
- @unique_only_check.setCheckable(true)
-
- @batch_mode_check = Qt::Action.new(tr('&Batch Mode'), self)
- @batch_mode_check_keyseq = Qt::KeySequence.new(tr('Ctrl+B'))
- @batch_mode_check.shortcut = @batch_mode_check_keyseq
- @batch_mode_check.statusTip = tr('Process multiple config files with the same input files')
- @batch_mode_check.setCheckable(true)
- @batch_mode_check.connect(SIGNAL('triggered()')) { batch_mode_changed() }
-
- # Item Menu Actions
- @item_edit = Qt::Action.new(tr('&Edit Items'), self)
- @item_edit_keyseq = Qt::KeySequence.new(tr('Ctrl+E'))
- @item_edit.shortcut = @item_edit_keyseq
- @item_edit.statusTip = tr('Options')
- @item_edit.connect(SIGNAL('triggered()')) { item_edit() }
-
- @item_delete = Qt::Action.new(tr('&Delete Items'), self)
- @item_delete.statusTip = tr('Options')
- @item_delete.connect(SIGNAL('triggered()')) { item_delete() }
- end
-
- def initialize_menus
- # File Menu
- @file_menu = menuBar.addMenu(tr('&File'))
- @file_menu.addAction(@open_config)
- @file_menu.addAction(@save_config)
- @file_menu.addSeparator()
- @file_menu.addAction(@file_options)
- @file_menu.addSeparator()
- @file_menu.addAction(@exit_action)
-
- # Mode Menu
- @mode_menu = menuBar.addMenu(tr('&Mode'))
- @mode_menu.addAction(@fill_down_check)
- @mode_menu.addAction(@matlab_header_check)
- @mode_menu.addAction(@share_columns_check)
- @mode_menu.addAction(@unique_only_check)
- @mode_menu.addAction(@batch_mode_check)
-
- # Item Menu
- @item_menu = menuBar.addMenu(tr('&Item'))
- @item_menu.addAction(@item_edit)
- @item_menu.addAction(@item_delete)
-
- # Help Menu
- @about_string = "COSMOS Telemetry Extractor allows processing of telemetry log files and breaking out specified items."
- initialize_help_menu()
- end
-
- def initialize_central_widget
- # Create the central widget
- @central_widget = Qt::Widget.new
- setCentralWidget(@central_widget)
-
- @top_layout = Qt::VBoxLayout.new
-
- @config_box = Qt::GroupBox.new("Configuration")
- @config_box_layout = Qt::VBoxLayout.new
- @config_box.setLayout(@config_box_layout)
- @top_layout.addWidget(@config_box)
-
- # Configuration File Selector
- @config_layout = Qt::HBoxLayout.new
- @config_layout_label = Qt::Label.new('Configuration File:')
- @config_layout.addWidget(@config_layout_label)
- @config_field = Qt::LineEdit.new
- @config_field.setReadOnly(true)
- @config_layout.addWidget(@config_field)
- @save_button = Qt::PushButton.new('Save...')
- @config_layout.addWidget(@save_button)
- @save_button.connect(SIGNAL('clicked()')) { handle_save_button() }
- @browse_button = Qt::PushButton.new('Browse...')
- @config_layout.addWidget(@browse_button)
- @browse_button.connect(SIGNAL('clicked()')) { handle_browse_button() }
- @config_box_layout.addLayout(@config_layout)
-
- # Separator before editor
- @sep1 = Qt::Frame.new(@central_widget)
- @sep1.setFrameStyle(Qt::Frame::HLine | Qt::Frame::Sunken)
- @config_box_layout.addWidget(@sep1)
-
- # Configuration File Editor
- @config_item_list = MyListWidget.new(self)
- @config_item_list.setContextMenuPolicy(Qt::CustomContextMenu)
- @config_item_list.setDragDropMode(Qt::AbstractItemView::InternalMove)
- @config_item_list.setSelectionMode(Qt::AbstractItemView::ExtendedSelection)
- @config_item_list.setMinimumHeight(150)
- # Allow either double click or enter / return key to bring up the item editor
- @config_item_list.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')) { item_list_editor() }
- @config_item_list.connect(SIGNAL('enterKeyPressed(int)')) { item_list_editor() }
- connect(@config_item_list, SIGNAL('customContextMenuRequested(const QPoint&)'), self, SLOT('context_menu(const QPoint&)'))
- @config_box_layout.addWidget(@config_item_list)
-
- # Telemetry Search
- @search_layout = Qt::HBoxLayout.new
- @search_box = FullTextSearchLineEdit.new(self)
- @search_box.setStyleSheet("padding-right: 20px;padding-left: 5px;background: url(#{File.join(Cosmos::PATH, 'data', 'search-14.png')});background-position: right;background-repeat: no-repeat;")
- @search_add_item_button = Qt::PushButton.new('Add Item')
- @search_add_item_button.connect(SIGNAL('clicked()')) do
- split_tlm = @search_box.text.to_s.split(" ")
- if split_tlm.length == 3
- target_name = split_tlm[0].to_s.upcase
- packet_name = split_tlm[1].to_s.upcase
- item_name = split_tlm[2].to_s.upcase
- begin
- System.telemetry.packet_and_item(target_name, packet_name, item_name)
- add_item_callback(target_name, packet_name, item_name)
- rescue
- # Does not exist
- end
- end
- end
- @search_add_packet_button = Qt::PushButton.new('Add Packet')
- @search_add_packet_button.connect(SIGNAL('clicked()')) do
- split_tlm = @search_box.text.to_s.split(" ")
- if split_tlm.length >= 2
- target_name = split_tlm[0].to_s.upcase
- packet_name = split_tlm[1].to_s.upcase
- begin
- System.telemetry.packet(target_name, packet_name)
- add_packet_callback(target_name, packet_name)
- rescue
- # Does not exist
- end
- end
- end
- @search_add_target_button = Qt::PushButton.new('Add Target')
- @search_add_target_button.connect(SIGNAL('clicked()')) do
- split_tlm = @search_box.text.to_s.split(" ")
- if split_tlm.length >= 1
- target_name = split_tlm[0].to_s.upcase
- if System.telemetry.target_names.include?(target_name)
- add_target_callback(target_name)
- end
- end
- end
- @search_layout.addWidget(@search_box)
- @search_layout.addWidget(@search_add_item_button)
- @search_layout.addWidget(@search_add_packet_button)
- @search_layout.addWidget(@search_add_target_button)
- @config_box_layout.addLayout(@search_layout)
-
- # Telemetry Chooser
- tlm_chooser_layout = Qt::BoxLayout.new(Qt::Horizontal)
- add_target_button = Qt::PushButton.new('Add Target')
- add_target_button.connect(SIGNAL('clicked()')) do
- add_target_callback(@telemetry_chooser.target_name)
- end
- tlm_chooser_layout.addWidget(add_target_button)
- add_packet_button = Qt::PushButton.new('Add Packet')
- add_packet_button.connect(SIGNAL('clicked()')) do
- add_packet_callback(@telemetry_chooser.target_name, @telemetry_chooser.packet_name)
- end
- tlm_chooser_layout.addWidget(add_packet_button)
- @telemetry_chooser = TelemetryChooser.new(self, Qt::Horizontal, true, true, false, true)
- @telemetry_chooser.button_text = 'Add Item'
- @telemetry_chooser.select_button_callback = method(:add_item_callback)
- tlm_chooser_layout.addWidget(@telemetry_chooser)
- @config_box_layout.addLayout(tlm_chooser_layout)
-
- # Text Item Chooser
- @text_item_chooser = TextItemChooser.new(self)
- @text_item_chooser.button_callback = method(:add_text_item_callback)
- @config_box_layout.addWidget(@text_item_chooser)
-
- # Downsample
- @downsample_entry = FloatChooser.new(self, 'Downsample Seconds:', 0.0, 0.0, nil, 20, true)
- @config_box_layout.addWidget(@downsample_entry)
-
- # Batch Configuration
- @batch_config_box = Qt::GroupBox.new("Batch Configuration")
- @batch_config_layout = Qt::GridLayout.new
- @batch_config_layout.setColumnStretch(1, 1)
- @batch_config_box.setLayout(@batch_config_layout)
- @top_layout.addWidget(@batch_config_box)
-
- row = 0
-
- # Chooser for Log Files
- label = Qt::Label.new('Config Files:')
- @batch_config_layout.addWidget(label, row, 0)
- @batch_fill_widget = Qt::Widget.new
- @batch_fill_widget.setMinimumWidth(360)
- @batch_config_layout.addWidget(@batch_fill_widget, row, 1, 0, 3)
- @batch_browse_button = Qt::PushButton.new('Browse...')
- @batch_browse_button.connect(SIGNAL('clicked()')) { handle_batch_browse_button() }
- @batch_config_layout.addWidget(@batch_browse_button, row, 2)
- @batch_remove_button = Qt::PushButton.new('Remove')
- @batch_remove_button.connect(SIGNAL('clicked()')) { handle_batch_remove_button() }
- @batch_config_layout.addWidget(@batch_remove_button, row, 3)
- row += 1
-
- @batch_filenames_entry = Qt::ListWidget.new(self)
- @batch_filenames_entry.setSelectionMode(Qt::AbstractItemView::ExtendedSelection)
- @batch_filenames_entry.setSortingEnabled(true)
- @batch_filenames_entry.setMinimumHeight(90)
- @batch_config_layout.addWidget(@batch_filenames_entry, row, 0, 3, 4)
- row += 3
-
- # Batch Name
- @batch_name_label = Qt::Label.new('Batch Name:')
- @batch_config_layout.addWidget(@batch_name_label, row, 0)
- @batch_name_entry = Qt::LineEdit.new
- @batch_name_entry.setMinimumWidth(340)
- @batch_config_layout.addWidget(@batch_name_entry, row, 1, 1, 3)
- row += 1
-
- @batch_config_box.hide
-
- @file_box = Qt::GroupBox.new("File Selection")
- @file_box_layout = Qt::VBoxLayout.new
- @file_box.setLayout(@file_box_layout)
- @top_layout.addWidget(@file_box)
-
- # Packet Log Frame
- @packet_log_frame = PacketLogFrame.new(self, @log_dir, System.default_packet_log_reader.new, @input_filenames, nil, true, true, true, Cosmos::TLM_FILE_PATTERN, Cosmos::TXT_FILE_PATTERN)
- @packet_log_frame.change_callback = method(:change_callback)
- @file_box_layout.addWidget(@packet_log_frame)
-
- # Process and Open Buttons
- @button_layout = Qt::HBoxLayout.new
-
- @process_button = Qt::PushButton.new('&Process Files')
- @process_button.connect(SIGNAL('clicked()')) { process_log_files() }
- @button_layout.addWidget(@process_button)
-
- @open_button = Qt::PushButton.new('&Open in Text Editor')
- @open_button.connect(SIGNAL('clicked()')) { open_button() }
- @open_button.setEnabled(false)
- @button_layout.addWidget(@open_button)
-
- if Kernel.is_windows?
- @open_excel_button = Qt::PushButton.new('&Open in Excel')
- @open_excel_button.connect(SIGNAL('clicked()')) { open_excel_button() }
- @open_excel_button.setEnabled(false)
- @button_layout.addWidget(@open_excel_button)
- end
- @top_layout.addLayout(@button_layout)
-
- @central_widget.setLayout(@top_layout)
- end # def initialize
-
- def self.post_options_parsed_hook(options)
- if options.input_files
- # Process config file
- raise "Configuration File must be specified for command line processing" unless options.config_file
-
- # Process the input file(s)
- tlm_extractor_config = TlmExtractorConfig.new(options.config_file)
- tlm_extractor_processor = TlmExtractorProcessor.new
- unless options.output_file
- filename = options.input_files[0]
- extension = File.extname(filename)
- filename_no_extension = filename[0..-(extension.length + 1)]
- if tlm_extractor_config.delimiter.to_s.strip == ','
- filename = filename_no_extension << '.csv'
- else
- filename = filename_no_extension << '.txt'
- end
- options.output_file = File.join(System.paths['LOGS'], File.basename(filename))
- end
-
- tlm_extractor_config.output_filename = options.output_file
- tlm_extractor_processor.process(options.input_files, [tlm_extractor_config])
- puts "Created #{options.output_file}"
- return false
- else
- return true
- end
- end
-
- # Runs the application
- def self.run(option_parser = nil, options = nil)
- Cosmos.catch_fatal_exception do
- unless option_parser and options
- option_parser, options = create_default_options()
- options.width = 700
- options.height = 425
- options.auto_size = false
- options.restore_size = false # always render this the correct size
- options.title = "Telemetry Extractor"
- option_parser.separator "Telemetry Extractor Specific Options:"
- option_parser.on("-c", "--config FILE", "Use the specified configuration file") do |arg|
- options.config_file = File.join(Cosmos::USERPATH, 'config', 'tools', 'tlm_extractor', arg)
- end
- option_parser.on("-i", "--input FILE", "Process the specified input file") do |arg|
- options.input_files ||= []
- if arg[0..0] != '/' and arg[1..1] != ':'
- # Relative path to default of log folder
- arg = File.join(System.paths['LOGS'], arg)
- end
- options.input_files << arg
- end
- option_parser.on("-o", "--output FILE", "Output results to the specified file") do |arg|
- options.output_file = arg
- end
- end
-
- super(option_parser, options)
- end
- end # def self.run
-
- def context_menu(point)
- @item_menu.exec(@config_item_list.mapToGlobal(point))
- end
-
- protected
-
- def sync_gui_to_config
- @tlm_extractor_config.matlab_header = @matlab_header_check.checked?
- @tlm_extractor_config.fill_down = @fill_down_check.checked?
- @tlm_extractor_config.share_columns = @share_columns_check.checked?
- @tlm_extractor_config.unique_only = @unique_only_check.checked?
- @tlm_extractor_config.downsample_seconds = @downsample_entry.value
- @tlm_extractor_config.output_filename = @packet_log_frame.output_filename
-
- @tlm_extractor_config.clear_items
- @config_item_list.each do |item|
- split_item = item.text.scan ConfigParser::PARSING_REGEX
- item_type = split_item[0]
- target_name_or_column_name = split_item[1]
- packet_name_or_text = split_item[2]
- item_name = split_item[3]
- value_type = split_item[4]
- if value_type
- value_type = value_type.upcase.intern
- else
- value_type = :CONVERTED
- end
- if item_type == 'ITEM'
- @tlm_extractor_config.add_item(target_name_or_column_name, packet_name_or_text, item_name, value_type)
- else
- @tlm_extractor_config.add_text(target_name_or_column_name.remove_quotes, packet_name_or_text.remove_quotes)
- end
- end
- end
-
- def sync_config_to_gui
- @matlab_header_check.setChecked(@tlm_extractor_config.matlab_header)
- @fill_down_check.setChecked(@tlm_extractor_config.fill_down)
- @share_columns_check.setChecked(@tlm_extractor_config.share_columns)
- @unique_only_check.setChecked(@tlm_extractor_config.unique_only)
- @downsample_entry.value = @tlm_extractor_config.downsample_seconds
-
- clear_config_item_list()
- @tlm_extractor_config.items.each do |item_type, target_name_or_column_name, packet_name_or_text, item_name, value_type|
- if item_type == 'ITEM'
- if value_type == :CONVERTED
- @config_item_list.addItem("#{item_type} #{target_name_or_column_name} #{packet_name_or_text} #{item_name}")
- else
- @config_item_list.addItem("#{item_type} #{target_name_or_column_name} #{packet_name_or_text} #{item_name} #{value_type}")
- end
- else
- @config_item_list.addItem("#{item_type} \"#{target_name_or_column_name}\" \"#{packet_name_or_text}\"")
- end
- end
- end
-
- ###############################################################################
- # File Menu Handlers
- ###############################################################################
-
- # Handles processing log files
- def process_log_files
- @cancel = false
- begin
- @tlm_extractor_processor.packet_log_reader = @packet_log_frame.packet_log_reader
- @input_filenames = @packet_log_frame.filenames.sort
- @batch_filenames = []
- output_extension = '.txt'
- batch_name = nil
- if @batch_mode_check.checked?
- batch_name = @batch_name_entry.text
- @batch_filenames_entry.each {|list_item| @batch_filenames << list_item.text}
- if @packet_log_frame.output_filename_filter == Cosmos::CSV_FILE_PATTERN
- output_extension = '.csv'
- else
- output_extension = '.txt'
- end
- end
- return unless pre_process_tests()
-
- # Configure Tlm Extractor Config
- sync_gui_to_config()
-
- start_time = Time.now
- ProgressDialog.execute(self, 'Log File Progress', 600, 300) do |progress_dialog|
- progress_dialog.cancel_callback = method(:cancel_callback)
- progress_dialog.enable_cancel_button
-
- begin
- current_input_file_index = -1
- current_config_file_index = -1
- start_packet_count = -1
- last_packet_count = -1
-
- if @batch_filenames.empty?
- process_method = :process
- process_args = [@input_filenames, [@tlm_extractor_config], @packet_log_frame.time_start, @packet_log_frame.time_end]
- else
- process_method = :process_batch
- process_args = [batch_name, @input_filenames, @log_dir, output_extension, @batch_filenames, @packet_log_frame.time_start, @packet_log_frame.time_end]
- end
-
- @tlm_extractor_processor.send(process_method, *process_args) do |input_file_index, packet_count, file_progress|
- # Handle Cancel
- break if @cancel
-
- # Handle Input File Change
- if input_file_index != current_input_file_index
- current_input_file_index = input_file_index
-
- if start_packet_count >= 0
- # Make sure some packets were found in the previous file
- if packet_count == start_packet_count
- # No packets found in previous file
- progress_dialog.append_text(" WARNING: No packets processed in #{File.basename(@input_filenames[input_file_index - 1])}")
- end
- end
- start_packet_count = packet_count
-
- progress_dialog.append_text("Processing File #{input_file_index + 1}/#{@input_filenames.length}: #{File.basename(@input_filenames[input_file_index])}")
- progress_dialog.set_step_progress(0.0)
- progress_dialog.set_overall_progress((input_file_index).to_f / @input_filenames.length.to_f)
- end
-
- # Save packet_count
- last_packet_count = packet_count
-
- # Handle Progress Reporting
- progress_dialog.set_step_progress(file_progress)
- end
- # Make sure some packets were found in the previous file
- if start_packet_count == last_packet_count
- # No packets found in previous file
- progress_dialog.append_text(" WARNING: No packets processed in #{File.basename(@input_filenames[-1])}")
- end
-
- rescue => error
- progress_dialog.append_text("Error processing:\n#{error.formatted}\n")
- ensure
- progress_dialog.set_step_progress(1.0) if !@cancel
- progress_dialog.set_overall_progress(1.0) if !@cancel
- progress_dialog.append_text("Runtime: #{Time.now - start_time} s")
- progress_dialog.complete
- if @batch_filenames.empty?
- Qt.execute_in_main_thread(true) do
- @open_button.setEnabled(true)
- @open_excel_button.setEnabled(true) if Kernel.is_windows?
- end
- end
- end
- end # ProgressDialog.execute
- rescue => error
- Qt::MessageBox.critical(self, 'Error!', "Error Processing Log File(s)\n#{error.formatted}")
- end
- end # def process_log_files
-
- # Handles options dialog
- def handle_options
- box = Qt::Dialog.new(self)
- box.setWindowTitle('Options')
- top_layout = Qt::VBoxLayout.new
-
- delimiter_layout = Qt::HBoxLayout.new
- delimiter_label = Qt::Label.new('Delimeter:')
- delimiter_layout.addWidget(delimiter_label)
- delimiter_field = Qt::LineEdit.new
- delimiter_layout.addWidget(delimiter_field)
- if @tlm_extractor_config.delimiter != "\t"
- delimiter_field.setText(@tlm_extractor_config.delimiter)
- else
- delimiter_field.setText('tab')
- end
- top_layout.addLayout(delimiter_layout)
-
- checkbox_layout = Qt::HBoxLayout.new
- checkbox_label = Qt::Label.new('Output filenames')
- checkbox_layout.addWidget(checkbox_label)
- checkbox_field = Qt::CheckBox.new
- if @tlm_extractor_config.print_filenames_to_output
- checkbox_field.setChecked(true)
- else
- checkbox_field.setChecked(false)
- end
- checkbox_layout.addWidget(checkbox_field)
- top_layout.addLayout(checkbox_layout)
-
- button_layout = Qt::HBoxLayout.new
- ok_button = Qt::PushButton.new('OK')
- ok_button.connect(SIGNAL('clicked()')) { box.accept }
- button_layout.addWidget(ok_button)
- button_layout.addStretch
- cancel_button = Qt::PushButton.new('CANCEL')
- cancel_button.connect(SIGNAL('clicked()')) { box.reject }
- button_layout.addWidget(cancel_button)
- top_layout.addLayout(button_layout)
-
- box.setLayout(top_layout)
- case box.exec
- when Qt::Dialog::Accepted
- delimiter = delimiter_field.text
- if delimiter == 'tab'
- delimiter = "\t"
- end
- @tlm_extractor_config.print_filenames_to_output = checkbox_field.checked?
- @tlm_extractor_config.delimiter = delimiter
- if @tlm_extractor_config.delimiter.to_s.strip == ','
- @packet_log_frame.output_filename_filter = Cosmos::CSV_FILE_PATTERN
- else
- @packet_log_frame.output_filename_filter = Cosmos::TXT_FILE_PATTERN
- end
- end
- box.dispose
- end
-
- def batch_mode_changed
- if @batch_mode_check.checked?
- @config_box.hide
- @batch_config_box.show
- @open_button.setEnabled(false)
- @open_excel_button.setEnabled(false) if Kernel.is_windows?
- @fill_down_check.setEnabled(false)
- @matlab_header_check.setEnabled(false)
- @share_columns_check.setEnabled(false)
- @unique_only_check.setEnabled(false)
- @open_config.setEnabled(false)
- @save_config.setEnabled(false)
- @file_options.setEnabled(false)
- @item_edit.setEnabled(false)
- @item_delete.setEnabled(false)
- @packet_log_frame.select_output_dir
- @packet_log_frame.output_filename = @log_dir
- else
- @config_box.show
- @batch_config_box.hide
- @fill_down_check.setEnabled(true)
- @matlab_header_check.setEnabled(true)
- @share_columns_check.setEnabled(true)
- @unique_only_check.setEnabled(true)
- @open_config.setEnabled(true)
- @save_config.setEnabled(true)
- @file_options.setEnabled(true)
- @item_edit.setEnabled(true)
- @item_delete.setEnabled(true)
- @packet_log_frame.select_output_file
- @packet_log_frame.output_filename = ''
- end
- end
-
- ###############################################################################
- # Item Menu Handlers
- ###############################################################################
-
- def item_edit
- item_list_editor()
- end
-
- def item_delete
- @config_item_list.remove_selected_items
- end
-
- ###############################################################################
- # Handlers
- ###############################################################################
-
- def handle_save_button
- filename = nil
- begin
- if @config_field.text.strip.length > 0
- filename = Qt::FileDialog.getSaveFileName(self, "Save Config File", @config_field.text, "Config Files (*.txt);;All Files (*)")
- else
- filename = Qt::FileDialog.getSaveFileName(self, "Save Config File", @config_dir, "Config Files (*.txt);;All Files (*)")
- end
- if filename and filename.length != 0
- sync_gui_to_config()
- @tlm_extractor_config.save(filename)
- @config_field.setText(filename)
- @config_dir = File.dirname(filename) + '/'
- end
- rescue => error
- Qt::MessageBox.critical(self, 'Error!', "Error Saving Configuration File: #{filename}\n#{error.formatted}")
- end
- end
-
- def handle_browse_button
- filename = Qt::FileDialog::getOpenFileName(self, "Open Config File:", @config_dir, "Config Files (*.txt);;All Files (*)")
- if filename and not filename.empty?
- @config_field.setText(filename)
- @config_dir = File.dirname(filename) + '/'
-
- begin
- process_config_file(filename)
- rescue => error
- Qt::MessageBox.critical(self, 'Error', "Error Processing Configuration File: #{filename}\n\n#{error.formatted}")
- end
- end
- end
-
- def item_list_editor
- done_items = false
- selected_items = @config_item_list.selected_items()
- if @config_item_list.currentItem and !selected_items.empty?
- selected_items.each do |item_index|
- dialog = Qt::Dialog.new(self)
- dialog.setWindowTitle("Edit Item")
- layout = Qt::VBoxLayout.new
- split_item = @config_item_list.item(item_index).text.scan ConfigParser::PARSING_REGEX
-
- if split_item[0] == 'ITEM' and !done_items
- label = Qt::Label.new("#{split_item[1]} #{split_item[2]} #{split_item[3]}")
- layout.addWidget(label)
-
- label = Qt::Label.new('Value Type:')
- layout.addWidget(label)
-
- box = Qt::ComboBox.new
- box.maxCount = FORMATTING_OPTIONS.length
- FORMATTING_OPTIONS.each {|item| box.addItem(item) }
- current_formatting = split_item[4]
- current_formatting = 'CONVERTED' unless current_formatting
- if FORMATTING_OPTIONS.index(current_formatting)
- box.currentIndex = FORMATTING_OPTIONS.index(current_formatting)
- end
- layout.addWidget(box)
-
- check_box = nil
- if selected_items.length > 1 and item_index == selected_items[0]
- check_box = Qt::CheckBox.new('Apply to All?')
- layout.addWidget(check_box)
- end
-
- button_layout = Qt::BoxLayout.new(Qt::Horizontal)
- ok = Qt::PushButton.new("Save")
- connect(ok, SIGNAL('clicked()'), dialog, SLOT('accept()'))
- button_layout.addWidget(ok)
- cancel = Qt::PushButton.new("Cancel")
- connect(cancel, SIGNAL('clicked()'), dialog, SLOT('reject()'))
- button_layout.addWidget(cancel)
- layout.addLayout(button_layout)
-
- dialog.setLayout(layout)
- if dialog.exec == Qt::Dialog::Accepted
- if box.currentIndex != -1
- set_indexes = [item_index]
- if check_box and check_box.checked?
- set_indexes = selected_items
- end
-
- set_indexes.each do |set_item_index|
- split_item = @config_item_list.item(set_item_index).text.scan ConfigParser::PARSING_REGEX
- if split_item[0] == 'ITEM'
- # Remove any formatting from the item by only keeping the first four strings
- @config_item_list.item(set_item_index).text = split_item[0..3].join(' ')
-
- if box.currentIndex != 0
- @config_item_list.item(set_item_index).text = "#{@config_item_list.item(set_item_index).text} #{FORMATTING_OPTIONS[box.currentIndex]}"
- end
- end
- end
- if set_indexes.length > 1
- done_items = true
- end
- end
- end
- elsif split_item[0] == 'TEXT'
- label = Qt::Label.new('Column Name:')
- layout.addWidget(label)
- column_name = Qt::LineEdit.new(split_item[1].remove_quotes)
- layout.addWidget(column_name)
- label = Qt::Label.new('Text:')
- layout.addWidget(label)
- text = Qt::LineEdit.new(split_item[2].remove_quotes)
- layout.addWidget(text)
-
- button_layout = Qt::BoxLayout.new(Qt::Horizontal)
- ok = Qt::PushButton.new("Save")
- connect(ok, SIGNAL('clicked()'), dialog, SLOT('accept()'))
- button_layout.addWidget(ok)
- cancel = Qt::PushButton.new("Cancel")
- connect(cancel, SIGNAL('clicked()'), dialog, SLOT('reject()'))
- button_layout.addWidget(cancel)
- layout.addLayout(button_layout)
-
- dialog.setLayout(layout)
- if dialog.exec == Qt::Dialog::Accepted
- # Remove any formatting from the item by only keeping the first four strings
- @config_item_list.item(item_index).text = "#{split_item[0]} \"#{column_name.text}\" \"#{text.text}\""
- end
- end
-
- dialog.dispose
- end
- end
- end
-
- def open_button
- Cosmos.open_in_text_editor(@packet_log_frame.output_filename)
- end
-
- def open_excel_button
- system("start Excel.exe \"#{@packet_log_frame.output_filename}\"")
- end
-
- def clear_config_item_list
- @config_item_list.clearItems
- end # def clear_data_object_list
-
- # Handles removing a selected filename
- def handle_batch_remove_button
- @batch_filenames_entry.remove_selected_items
- end
-
- # Handles browsing for log files
- def handle_batch_browse_button
- Cosmos.set_working_dir do
- filenames = Qt::FileDialog::getOpenFileNames(
- self, "Select Config File(s):", @config_dir, Cosmos::TXT_FILE_PATTERN)
- if filenames and not filenames.empty?
- @config_dir = File.dirname(filenames[0]) + '/'
- filenames.each {|filename| @batch_filenames_entry.addItem(filename) if @batch_filenames_entry.findItems(filename, Qt::MatchExactly).empty? }
- end
- end
- end
-
- ###############################################################################
- # Additional Callbacks
- ###############################################################################
-
- def cancel_callback(progress_dialog = nil)
- @cancel = true
- return true, false
- end
-
- def add_target_callback(target_name)
- packets = System.telemetry.packets(target_name)
- packets.each do |packet_name, packet|
- packet.sorted_items.each do |item|
- @config_item_list.addItem("ITEM #{target_name} #{packet_name} #{item.name}")
- end
- end
- end
-
- def add_packet_callback(target_name, packet_name)
- packet = System.telemetry.packet(target_name, packet_name)
- packet.sorted_items.each do |item|
- @config_item_list.addItem("ITEM #{target_name} #{packet_name} #{item.name}")
- end
- end
-
- def add_item_callback(target_name, packet_name, item_name)
- @config_item_list.addItem("ITEM #{target_name} #{packet_name} #{item_name}")
- end
-
- def add_text_item_callback(column_name, text)
- @config_item_list.addItem("TEXT \"#{column_name}\" \"#{text}\"")
- end
-
- ###############################################################################
- # Helper Methods
- ###############################################################################
-
- def pre_process_tests
- if !@batch_mode_check.checked?
- # Normal Mode
-
- if @config_item_list.count < 1
- Qt::MessageBox.critical(self, 'Error', 'Please select at least 1 item')
- return false
- end
-
- unless @packet_log_frame.output_filename
- Qt::MessageBox.critical(self, 'Error', 'No Output File Selected')
- return false
- end
-
- if File.exist?(@packet_log_frame.output_filename)
- result = Qt::MessageBox.warning(self, 'Warning!', 'Output File Already Exists. Overwrite?', Qt::MessageBox::Yes | Qt::MessageBox::No)
- return false if result == Qt::MessageBox::No
- end
- else
- # Batch Mode
-
- if !@batch_name_entry.text or @batch_name_entry.text.strip.empty?
- Qt::MessageBox.critical(self, 'Error', 'Batch Name is Required')
- return false
- end
-
- unless @batch_filenames and @batch_filenames[0]
- Qt::MessageBox.critical(self, 'Error', 'Please select at least 1 config file')
- return false
- end
- end
-
- unless @input_filenames and @input_filenames[0]
- Qt::MessageBox.critical(self, 'Error', 'Please select at least 1 input file')
- return false
- end
-
- # Validate configurations exist for input filenames
- @input_filenames.each do |input_filename|
- Cosmos.check_log_configuration(@tlm_extractor_processor.packet_log_reader, input_filename)
- end
-
- #Validate config information
- @tlm_extractor_processor.packet_log_reader.open(@input_filenames[0])
- @tlm_extractor_processor.packet_log_reader.close
-
- @config_item_list.each do |item|
- split_item = item.text.split
- item_type = split_item[0]
- target_name = split_item[1]
- packet_name = split_item[2]
- item_name = split_item[3]
-
- if item_type == 'ITEM'
- # Verify Packet
- packet = nil
- begin
- packet = System.telemetry.packet(target_name, packet_name)
- rescue
- Qt::MessageBox.critical(self, 'Error!', "Unknown Packet #{target_name} #{packet_name} specified")
- return false
- end
-
- # Verify Item
- begin
- packet.get_item(item_name)
- rescue
- Qt::MessageBox.critical(self, 'Error!', "Item #{item_name} not present in packet")
- return false
- end
- end
- end
-
- true
- end
-
- def process_config_file(filename)
- @tlm_extractor_config.restore(filename)
- sync_config_to_gui()
- end
-
- def change_callback(item_changed)
- if item_changed == :INPUT_FILES
- if !@batch_mode_check.checked?
- filename = @packet_log_frame.filenames[0]
- if filename
- extension = File.extname(filename)
- filename_no_extension = filename[0..-(extension.length + 1)]
- if @packet_log_frame.output_filename_filter == Cosmos::CSV_FILE_PATTERN
- filename = filename_no_extension << '.csv'
- else
- filename = filename_no_extension << '.txt'
- end
- @packet_log_frame.output_filename = filename
- end
- end
- elsif item_changed == :OUTPUT_FILE
- output_filename = @packet_log_frame.output_filename
- if output_filename and !output_filename.to_s.strip.empty?
- @log_dir = File.dirname(output_filename)
- end
- elsif item_changed == :OUTPUT_DIR
- output_filename = @packet_log_frame.output_filename
- if output_filename and !output_filename.to_s.strip.empty?
- @log_dir = output_filename
- end
- end
- end
-
- end # class TlmExtractor
-
-end # module Cosmos
+# encoding: ascii-8bit
+
+# Copyright 2014 Ball Aerospace & Technologies Corp.
+# All Rights Reserved.
+#
+# This program is free software; you can modify and/or redistribute it
+# under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 3 with
+# attribution addendums as found in the LICENSE.txt
+
+require 'cosmos'
+Cosmos.catch_fatal_exception do
+ require 'cosmos/tools/tlm_extractor/tlm_extractor_processor'
+ require 'cosmos/tools/tlm_extractor/text_item_chooser'
+ require 'cosmos/gui/qt_tool'
+ require 'cosmos/gui/choosers/telemetry_chooser'
+ require 'cosmos/gui/choosers/float_chooser'
+ require 'cosmos/gui/dialogs/splash'
+ require 'cosmos/gui/widgets/packet_log_frame'
+ require 'cosmos/gui/dialogs/progress_dialog'
+ require 'cosmos/gui/widgets/full_text_search_line_edit'
+end
+
+module Cosmos
+
+ # TlmExtractor class
+ #
+ # This class implements the TlmExtractor. This application breaks a binary log of telemetry
+ # into a csv type file
+ #
+ class TlmExtractor < QtTool
+ slots 'context_menu(const QPoint&)'
+
+ FORMATTING_OPTIONS = %w(CONVERTED RAW FORMATTED WITH_UNITS)
+
+ class MyListWidget < Qt::ListWidget
+ signals 'enterKeyPressed(int)'
+
+ def keyPressEvent(event)
+ case event.key
+ when Qt::Key_Delete, Qt::Key_Backspace
+ remove_selected_items()
+ when Qt::Key_Return, Qt::Key_Enter
+ emit enterKeyPressed(currentRow())
+ end
+ super(event)
+ end
+
+ def remove_selected_items
+ indexes = selected_items()
+ indexes.reverse_each do |index|
+ item = takeItem(index)
+ item.dispose if item
+ end
+ end
+
+ # Returns an array with the indexes of the selected items
+ def selected_items
+ selected = []
+ index = 0
+ self.each do |list_item|
+ selected << index if list_item.selected?
+ index += 1
+ end
+ selected
+ end
+ end
+
+ # Constructor
+ def initialize(options)
+ super(options) # MUST BE FIRST - All code before super is executed twice in RubyQt Based classes
+ Cosmos.load_cosmos_icon("tlm_extractor.png")
+
+ # Define instance variables
+ @input_filenames = []
+ @log_dir = System.paths['LOGS']
+ @config_dir = File.join(Cosmos::USERPATH, 'config', 'tools', 'tlm_extractor', '')
+ @cancel = false
+
+ initialize_actions()
+ initialize_menus()
+ initialize_central_widget()
+ complete_initialize()
+
+ # Bring up slash screen for long duration tasks after creation
+ Splash.execute(self) do |splash|
+ # Configure CosmosConfig to interact with splash screen
+ ConfigParser.splash = splash
+
+ System.telemetry
+ @search_box.completion_list = System.telemetry.all_item_strings(true, splash)
+ @tlm_extractor_config = TlmExtractorConfig.new(options.config_file)
+ @tlm_extractor_processor = TlmExtractorProcessor.new
+ Qt.execute_in_main_thread(true) do
+ @telemetry_chooser.update
+ sync_config_to_gui()
+ end
+
+ # Unconfigure CosmosConfig to interact with splash screen
+ ConfigParser.splash = nil
+ end
+ end
+
+ def initialize_actions
+ super()
+
+ # File Menu Actions
+ @open_config = Qt::Action.new(tr('Open &Config'), self)
+ @open_config.statusTip = tr('Open configuration file')
+ @open_config.connect(SIGNAL('triggered()')) { handle_browse_button() }
+
+ @save_config = Qt::Action.new(tr('&Save Config'), self)
+ @save_config_keyseq = Qt::KeySequence.new(tr('Ctrl+S'))
+ @save_config.shortcut = @save_config_keyseq
+ @save_config.statusTip = tr('Save current configuration')
+ @save_config.connect(SIGNAL('triggered()')) { handle_save_button() }
+
+ @file_options = Qt::Action.new(tr('O&ptions'), self)
+ @file_options.statusTip = tr('Open the options dialog')
+ @file_options.connect(SIGNAL('triggered()')) { handle_options() }
+
+ # Mode Menu Actions
+ @fill_down_check = Qt::Action.new(tr('&Fill Down'), self)
+ @fill_down_check_keyseq = Qt::KeySequence.new(tr('Ctrl+F'))
+ @fill_down_check.shortcut = @fill_down_check_keyseq
+ @fill_down_check.statusTip = tr('Fill Down')
+ @fill_down_check.setCheckable(true)
+
+ @matlab_header_check = Qt::Action.new(tr('&Matlab Header'), self)
+ @matlab_header_check_keyseq = Qt::KeySequence.new(tr('Ctrl+M'))
+ @matlab_header_check.shortcut = @matlab_header_check_keyseq
+ @matlab_header_check.statusTip = tr('Add a Matlab header to the output data')
+ @matlab_header_check.setCheckable(true)
+
+ @share_columns_check = Qt::Action.new(tr('&Share Columns'), self)
+ @share_columns_check.statusTip = tr('Share columns for items with the same name')
+ @share_columns_check.setCheckable(true)
+
+ @unique_only_check = Qt::Action.new(tr('&Unique Only'), self)
+ @unique_only_check_keyseq = Qt::KeySequence.new(tr('Ctrl+U'))
+ @unique_only_check.shortcut = @unique_only_check_keyseq
+ @unique_only_check.statusTip = tr('Only output rows where data has changed')
+ @unique_only_check.setCheckable(true)
+
+ @batch_mode_check = Qt::Action.new(tr('&Batch Mode'), self)
+ @batch_mode_check_keyseq = Qt::KeySequence.new(tr('Ctrl+B'))
+ @batch_mode_check.shortcut = @batch_mode_check_keyseq
+ @batch_mode_check.statusTip = tr('Process multiple config files with the same input files')
+ @batch_mode_check.setCheckable(true)
+ @batch_mode_check.connect(SIGNAL('triggered()')) { batch_mode_changed() }
+
+ # Item Menu Actions
+ @item_edit = Qt::Action.new(tr('&Edit Items'), self)
+ @item_edit_keyseq = Qt::KeySequence.new(tr('Ctrl+E'))
+ @item_edit.shortcut = @item_edit_keyseq
+ @item_edit.statusTip = tr('Options')
+ @item_edit.connect(SIGNAL('triggered()')) { item_edit() }
+
+ @item_delete = Qt::Action.new(tr('&Delete Items'), self)
+ @item_delete.statusTip = tr('Options')
+ @item_delete.connect(SIGNAL('triggered()')) { item_delete() }
+ end
+
+ def initialize_menus
+ # File Menu
+ @file_menu = menuBar.addMenu(tr('&File'))
+ @file_menu.addAction(@open_config)
+ @file_menu.addAction(@save_config)
+ @file_menu.addSeparator()
+ @file_menu.addAction(@file_options)
+ @file_menu.addSeparator()
+ @file_menu.addAction(@exit_action)
+
+ # Mode Menu
+ @mode_menu = menuBar.addMenu(tr('&Mode'))
+ @mode_menu.addAction(@fill_down_check)
+ @mode_menu.addAction(@matlab_header_check)
+ @mode_menu.addAction(@share_columns_check)
+ @mode_menu.addAction(@unique_only_check)
+ @mode_menu.addAction(@batch_mode_check)
+
+ # Item Menu
+ @item_menu = menuBar.addMenu(tr('&Item'))
+ @item_menu.addAction(@item_edit)
+ @item_menu.addAction(@item_delete)
+
+ # Help Menu
+ @about_string = "COSMOS Telemetry Extractor allows processing of telemetry log files and breaking out specified items."
+ initialize_help_menu()
+ end
+
+ def initialize_central_widget
+ # Create the central widget
+ @central_widget = Qt::Widget.new
+ setCentralWidget(@central_widget)
+
+ @top_layout = Qt::VBoxLayout.new
+
+ @config_box = Qt::GroupBox.new("Configuration")
+ @config_box_layout = Qt::VBoxLayout.new
+ @config_box.setLayout(@config_box_layout)
+ @top_layout.addWidget(@config_box)
+
+ # Configuration File Selector
+ @config_layout = Qt::HBoxLayout.new
+ @config_layout_label = Qt::Label.new('Configuration File:')
+ @config_layout.addWidget(@config_layout_label)
+ @config_field = Qt::LineEdit.new
+ @config_field.setReadOnly(true)
+ @config_layout.addWidget(@config_field)
+ @save_button = Qt::PushButton.new('Save...')
+ @config_layout.addWidget(@save_button)
+ @save_button.connect(SIGNAL('clicked()')) { handle_save_button() }
+ @browse_button = Qt::PushButton.new('Browse...')
+ @config_layout.addWidget(@browse_button)
+ @browse_button.connect(SIGNAL('clicked()')) { handle_browse_button() }
+ @config_box_layout.addLayout(@config_layout)
+
+ # Separator before editor
+ @sep1 = Qt::Frame.new(@central_widget)
+ @sep1.setFrameStyle(Qt::Frame::HLine | Qt::Frame::Sunken)
+ @config_box_layout.addWidget(@sep1)
+
+ # Configuration File Editor
+ @config_item_list = MyListWidget.new(self)
+ @config_item_list.setContextMenuPolicy(Qt::CustomContextMenu)
+ @config_item_list.setDragDropMode(Qt::AbstractItemView::InternalMove)
+ @config_item_list.setSelectionMode(Qt::AbstractItemView::ExtendedSelection)
+ @config_item_list.setMinimumHeight(150)
+ # Allow either double click or enter / return key to bring up the item editor
+ @config_item_list.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')) { item_list_editor() }
+ @config_item_list.connect(SIGNAL('enterKeyPressed(int)')) { item_list_editor() }
+ connect(@config_item_list, SIGNAL('customContextMenuRequested(const QPoint&)'), self, SLOT('context_menu(const QPoint&)'))
+ @config_box_layout.addWidget(@config_item_list)
+
+ # Telemetry Search
+ @search_layout = Qt::HBoxLayout.new
+ @search_box = FullTextSearchLineEdit.new(self)
+ @search_box.setStyleSheet("padding-right: 20px;padding-left: 5px;background: url(#{File.join(Cosmos::PATH, 'data', 'search-14.png')});background-position: right;background-repeat: no-repeat;")
+ @search_add_item_button = Qt::PushButton.new('Add Item')
+ @search_add_item_button.connect(SIGNAL('clicked()')) do
+ split_tlm = @search_box.text.to_s.split(" ")
+ if split_tlm.length == 3
+ target_name = split_tlm[0].to_s.upcase
+ packet_name = split_tlm[1].to_s.upcase
+ item_name = split_tlm[2].to_s.upcase
+ begin
+ System.telemetry.packet_and_item(target_name, packet_name, item_name)
+ add_item_callback(target_name, packet_name, item_name)
+ rescue
+ # Does not exist
+ end
+ end
+ end
+ @search_add_packet_button = Qt::PushButton.new('Add Packet')
+ @search_add_packet_button.connect(SIGNAL('clicked()')) do
+ split_tlm = @search_box.text.to_s.split(" ")
+ if split_tlm.length >= 2
+ target_name = split_tlm[0].to_s.upcase
+ packet_name = split_tlm[1].to_s.upcase
+ begin
+ System.telemetry.packet(target_name, packet_name)
+ add_packet_callback(target_name, packet_name)
+ rescue
+ # Does not exist
+ end
+ end
+ end
+ @search_add_target_button = Qt::PushButton.new('Add Target')
+ @search_add_target_button.connect(SIGNAL('clicked()')) do
+ split_tlm = @search_box.text.to_s.split(" ")
+ if split_tlm.length >= 1
+ target_name = split_tlm[0].to_s.upcase
+ if System.telemetry.target_names.include?(target_name)
+ add_target_callback(target_name)
+ end
+ end
+ end
+ @search_layout.addWidget(@search_box)
+ @search_layout.addWidget(@search_add_item_button)
+ @search_layout.addWidget(@search_add_packet_button)
+ @search_layout.addWidget(@search_add_target_button)
+ @config_box_layout.addLayout(@search_layout)
+
+ # Telemetry Chooser
+ tlm_chooser_layout = Qt::BoxLayout.new(Qt::Horizontal)
+ add_target_button = Qt::PushButton.new('Add Target')
+ add_target_button.connect(SIGNAL('clicked()')) do
+ add_target_callback(@telemetry_chooser.target_name)
+ end
+ tlm_chooser_layout.addWidget(add_target_button)
+ add_packet_button = Qt::PushButton.new('Add Packet')
+ add_packet_button.connect(SIGNAL('clicked()')) do
+ add_packet_callback(@telemetry_chooser.target_name, @telemetry_chooser.packet_name)
+ end
+ tlm_chooser_layout.addWidget(add_packet_button)
+ @telemetry_chooser = TelemetryChooser.new(self, Qt::Horizontal, true, true, false, true)
+ @telemetry_chooser.button_text = 'Add Item'
+ @telemetry_chooser.select_button_callback = method(:add_item_callback)
+ tlm_chooser_layout.addWidget(@telemetry_chooser)
+ @config_box_layout.addLayout(tlm_chooser_layout)
+
+ # Text Item Chooser
+ @text_item_chooser = TextItemChooser.new(self)
+ @text_item_chooser.button_callback = method(:add_text_item_callback)
+ @config_box_layout.addWidget(@text_item_chooser)
+
+ # Downsample
+ @downsample_entry = FloatChooser.new(self, 'Downsample Seconds:', 0.0, 0.0, nil, 20, true)
+ @config_box_layout.addWidget(@downsample_entry)
+
+ # Batch Configuration
+ @batch_config_box = Qt::GroupBox.new("Batch Configuration")
+ @batch_config_layout = Qt::GridLayout.new
+ @batch_config_layout.setColumnStretch(1, 1)
+ @batch_config_box.setLayout(@batch_config_layout)
+ @top_layout.addWidget(@batch_config_box)
+
+ row = 0
+
+ # Chooser for Log Files
+ label = Qt::Label.new('Config Files:')
+ @batch_config_layout.addWidget(label, row, 0)
+ @batch_fill_widget = Qt::Widget.new
+ @batch_fill_widget.setMinimumWidth(360)
+ @batch_config_layout.addWidget(@batch_fill_widget, row, 1, 0, 3)
+ @batch_browse_button = Qt::PushButton.new('Browse...')
+ @batch_browse_button.connect(SIGNAL('clicked()')) { handle_batch_browse_button() }
+ @batch_config_layout.addWidget(@batch_browse_button, row, 2)
+ @batch_remove_button = Qt::PushButton.new('Remove')
+ @batch_remove_button.connect(SIGNAL('clicked()')) { handle_batch_remove_button() }
+ @batch_config_layout.addWidget(@batch_remove_button, row, 3)
+ row += 1
+
+ @batch_filenames_entry = Qt::ListWidget.new(self)
+ @batch_filenames_entry.setSelectionMode(Qt::AbstractItemView::ExtendedSelection)
+ @batch_filenames_entry.setSortingEnabled(true)
+ @batch_filenames_entry.setMinimumHeight(90)
+ @batch_config_layout.addWidget(@batch_filenames_entry, row, 0, 3, 4)
+ row += 3
+
+ # Batch Name
+ @batch_name_label = Qt::Label.new('Batch Name:')
+ @batch_config_layout.addWidget(@batch_name_label, row, 0)
+ @batch_name_entry = Qt::LineEdit.new
+ @batch_name_entry.setMinimumWidth(340)
+ @batch_config_layout.addWidget(@batch_name_entry, row, 1, 1, 3)
+ row += 1
+
+ @batch_config_box.hide
+
+ @file_box = Qt::GroupBox.new("File Selection")
+ @file_box_layout = Qt::VBoxLayout.new
+ @file_box.setLayout(@file_box_layout)
+ @top_layout.addWidget(@file_box)
+
+ # Packet Log Frame
+ @packet_log_frame = PacketLogFrame.new(self, @log_dir, System.default_packet_log_reader.new, @input_filenames, nil, true, true, true, Cosmos::TLM_FILE_PATTERN, Cosmos::TXT_FILE_PATTERN)
+ @packet_log_frame.change_callback = method(:change_callback)
+ @file_box_layout.addWidget(@packet_log_frame)
+
+ # Process and Open Buttons
+ @button_layout = Qt::HBoxLayout.new
+
+ @process_button = Qt::PushButton.new('&Process Files')
+ @process_button.connect(SIGNAL('clicked()')) { process_log_files() }
+ @button_layout.addWidget(@process_button)
+
+ @open_button = Qt::PushButton.new('&Open in Text Editor')
+ @open_button.connect(SIGNAL('clicked()')) { open_button() }
+ @open_button.setEnabled(false)
+ @button_layout.addWidget(@open_button)
+
+ if Kernel.is_windows?
+ @open_excel_button = Qt::PushButton.new('&Open in Excel')
+ @open_excel_button.connect(SIGNAL('clicked()')) { open_excel_button() }
+ @open_excel_button.setEnabled(false)
+ @button_layout.addWidget(@open_excel_button)
+ end
+ @top_layout.addLayout(@button_layout)
+
+ @central_widget.setLayout(@top_layout)
+ end # def initialize
+
+ def self.post_options_parsed_hook(options)
+ if options.input_files
+ # Process config file
+ raise "Configuration File must be specified for command line processing" unless options.config_file
+
+ # Process the input file(s)
+ tlm_extractor_config = TlmExtractorConfig.new(options.config_file)
+ tlm_extractor_processor = TlmExtractorProcessor.new
+ unless options.output_file
+ filename = options.input_files[0]
+ extension = File.extname(filename)
+ filename_no_extension = filename[0..-(extension.length + 1)]
+ if tlm_extractor_config.delimiter.to_s.strip == ','
+ filename = filename_no_extension << '.csv'
+ else
+ filename = filename_no_extension << '.txt'
+ end
+ options.output_file = File.join(System.paths['LOGS'], File.basename(filename))
+ end
+
+ tlm_extractor_config.output_filename = options.output_file
+ tlm_extractor_processor.process(options.input_files, [tlm_extractor_config])
+ puts "Created #{options.output_file}"
+ return false
+ else
+ return true
+ end
+ end
+
+ # Runs the application
+ def self.run(option_parser = nil, options = nil)
+ Cosmos.catch_fatal_exception do
+ unless option_parser and options
+ option_parser, options = create_default_options()
+ options.width = 700
+ options.height = 425
+ options.auto_size = false
+ options.restore_size = false # always render this the correct size
+ options.title = "Telemetry Extractor"
+ option_parser.separator "Telemetry Extractor Specific Options:"
+ option_parser.on("-c", "--config FILE", "Use the specified configuration file") do |arg|
+ options.config_file = File.join(Cosmos::USERPATH, 'config', 'tools', 'tlm_extractor', arg)
+ end
+ option_parser.on("-i", "--input FILE", "Process the specified input file") do |arg|
+ options.input_files ||= []
+ if arg[0..0] != '/' and arg[1..1] != ':'
+ # Relative path to default of log folder
+ arg = File.join(System.paths['LOGS'], arg)
+ end
+ options.input_files << arg
+ end
+ option_parser.on("-o", "--output FILE", "Output results to the specified file") do |arg|
+ options.output_file = arg
+ end
+ end
+
+ super(option_parser, options)
+ end
+ end # def self.run
+
+ def context_menu(point)
+ @item_menu.exec(@config_item_list.mapToGlobal(point))
+ end
+
+ protected
+
+ def sync_gui_to_config
+ @tlm_extractor_config.matlab_header = @matlab_header_check.checked?
+ @tlm_extractor_config.fill_down = @fill_down_check.checked?
+ @tlm_extractor_config.share_columns = @share_columns_check.checked?
+ @tlm_extractor_config.unique_only = @unique_only_check.checked?
+ @tlm_extractor_config.downsample_seconds = @downsample_entry.value
+ @tlm_extractor_config.output_filename = @packet_log_frame.output_filename
+
+ @tlm_extractor_config.clear_items
+ @config_item_list.each do |item|
+ split_item = item.text.scan ConfigParser::PARSING_REGEX
+ item_type = split_item[0]
+ target_name_or_column_name = split_item[1]
+ packet_name_or_text = split_item[2]
+ item_name = split_item[3]
+ value_type = split_item[4]
+ if value_type
+ value_type = value_type.upcase.intern
+ else
+ value_type = :CONVERTED
+ end
+ if item_type == 'ITEM'
+ @tlm_extractor_config.add_item(target_name_or_column_name, packet_name_or_text, item_name, value_type)
+ else
+ @tlm_extractor_config.add_text(target_name_or_column_name.remove_quotes, packet_name_or_text.remove_quotes)
+ end
+ end
+ end
+
+ def sync_config_to_gui
+ @matlab_header_check.setChecked(@tlm_extractor_config.matlab_header)
+ @fill_down_check.setChecked(@tlm_extractor_config.fill_down)
+ @share_columns_check.setChecked(@tlm_extractor_config.share_columns)
+ @unique_only_check.setChecked(@tlm_extractor_config.unique_only)
+ @downsample_entry.value = @tlm_extractor_config.downsample_seconds
+
+ clear_config_item_list()
+ @tlm_extractor_config.items.each do |item_type, target_name_or_column_name, packet_name_or_text, item_name, value_type|
+ if item_type == 'ITEM'
+ if value_type == :CONVERTED
+ @config_item_list.addItem("#{item_type} #{target_name_or_column_name} #{packet_name_or_text} #{item_name}")
+ else
+ @config_item_list.addItem("#{item_type} #{target_name_or_column_name} #{packet_name_or_text} #{item_name} #{value_type}")
+ end
+ else
+ @config_item_list.addItem("#{item_type} \"#{target_name_or_column_name}\" \"#{packet_name_or_text}\"")
+ end
+ end
+ end
+
+ ###############################################################################
+ # File Menu Handlers
+ ###############################################################################
+
+ # Handles processing log files
+ def process_log_files
+ @cancel = false
+ begin
+ @tlm_extractor_processor.packet_log_reader = @packet_log_frame.packet_log_reader
+ @input_filenames = @packet_log_frame.filenames.sort
+ @batch_filenames = []
+ output_extension = '.txt'
+ batch_name = nil
+ if @batch_mode_check.checked?
+ batch_name = @batch_name_entry.text
+ @batch_filenames_entry.each {|list_item| @batch_filenames << list_item.text}
+ if @packet_log_frame.output_filename_filter == Cosmos::CSV_FILE_PATTERN
+ output_extension = '.csv'
+ else
+ output_extension = '.txt'
+ end
+ end
+ return unless pre_process_tests()
+
+ # Configure Tlm Extractor Config
+ sync_gui_to_config()
+
+ start_time = Time.now
+ ProgressDialog.execute(self, 'Log File Progress', 600, 300) do |progress_dialog|
+ progress_dialog.cancel_callback = method(:cancel_callback)
+ progress_dialog.enable_cancel_button
+
+ begin
+ current_input_file_index = -1
+ current_config_file_index = -1
+ start_packet_count = -1
+ last_packet_count = -1
+
+ if @batch_filenames.empty?
+ process_method = :process
+ process_args = [@input_filenames, [@tlm_extractor_config], @packet_log_frame.time_start, @packet_log_frame.time_end]
+ else
+ process_method = :process_batch
+ process_args = [batch_name, @input_filenames, @log_dir, output_extension, @batch_filenames, @packet_log_frame.time_start, @packet_log_frame.time_end]
+ end
+
+ @tlm_extractor_processor.send(process_method, *process_args) do |input_file_index, packet_count, file_progress|
+ # Handle Cancel
+ break if @cancel
+
+ # Handle Input File Change
+ if input_file_index != current_input_file_index
+ current_input_file_index = input_file_index
+
+ if start_packet_count >= 0
+ # Make sure some packets were found in the previous file
+ if packet_count == start_packet_count
+ # No packets found in previous file
+ progress_dialog.append_text(" WARNING: No packets processed in #{File.basename(@input_filenames[input_file_index - 1])}")
+ end
+ end
+ start_packet_count = packet_count
+
+ progress_dialog.append_text("Processing File #{input_file_index + 1}/#{@input_filenames.length}: #{File.basename(@input_filenames[input_file_index])}")
+ progress_dialog.set_step_progress(0.0)
+ progress_dialog.set_overall_progress((input_file_index).to_f / @input_filenames.length.to_f)
+ end
+
+ # Save packet_count
+ last_packet_count = packet_count
+
+ # Handle Progress Reporting
+ progress_dialog.set_step_progress(file_progress)
+ end
+ # Make sure some packets were found in the previous file
+ if start_packet_count == last_packet_count
+ # No packets found in previous file
+ progress_dialog.append_text(" WARNING: No packets processed in #{File.basename(@input_filenames[-1])}")
+ end
+
+ rescue => error
+ progress_dialog.append_text("Error processing:\n#{error.formatted}\n")
+ ensure
+ progress_dialog.set_step_progress(1.0) if !@cancel
+ progress_dialog.set_overall_progress(1.0) if !@cancel
+ progress_dialog.append_text("Runtime: #{Time.now - start_time} s")
+ progress_dialog.complete
+ if @batch_filenames.empty?
+ Qt.execute_in_main_thread(true) do
+ @open_button.setEnabled(true)
+ @open_excel_button.setEnabled(true) if Kernel.is_windows?
+ end
+ end
+ end
+ end # ProgressDialog.execute
+ rescue => error
+ Qt::MessageBox.critical(self, 'Error!', "Error Processing Log File(s)\n#{error.formatted}")
+ end
+ end # def process_log_files
+
+ # Handles options dialog
+ def handle_options
+ box = Qt::Dialog.new(self)
+ box.setWindowTitle('Options')
+ top_layout = Qt::VBoxLayout.new
+
+ delimiter_layout = Qt::HBoxLayout.new
+ delimiter_label = Qt::Label.new('Delimeter:')
+ delimiter_layout.addWidget(delimiter_label)
+ delimiter_field = Qt::LineEdit.new
+ delimiter_layout.addWidget(delimiter_field)
+ if @tlm_extractor_config.delimiter != "\t"
+ delimiter_field.setText(@tlm_extractor_config.delimiter)
+ else
+ delimiter_field.setText('tab')
+ end
+ top_layout.addLayout(delimiter_layout)
+
+ checkbox_layout = Qt::HBoxLayout.new
+ checkbox_label = Qt::Label.new('Output filenames')
+ checkbox_layout.addWidget(checkbox_label)
+ checkbox_field = Qt::CheckBox.new
+ if @tlm_extractor_config.print_filenames_to_output
+ checkbox_field.setChecked(true)
+ else
+ checkbox_field.setChecked(false)
+ end
+ checkbox_layout.addWidget(checkbox_field)
+ top_layout.addLayout(checkbox_layout)
+
+ button_layout = Qt::HBoxLayout.new
+ ok_button = Qt::PushButton.new('OK')
+ ok_button.connect(SIGNAL('clicked()')) { box.accept }
+ button_layout.addWidget(ok_button)
+ button_layout.addStretch
+ cancel_button = Qt::PushButton.new('CANCEL')
+ cancel_button.connect(SIGNAL('clicked()')) { box.reject }
+ button_layout.addWidget(cancel_button)
+ top_layout.addLayout(button_layout)
+
+ box.setLayout(top_layout)
+ case box.exec
+ when Qt::Dialog::Accepted
+ delimiter = delimiter_field.text
+ if delimiter == 'tab'
+ delimiter = "\t"
+ end
+ @tlm_extractor_config.print_filenames_to_output = checkbox_field.checked?
+ @tlm_extractor_config.delimiter = delimiter
+ if @tlm_extractor_config.delimiter.to_s.strip == ','
+ @packet_log_frame.output_filename_filter = Cosmos::CSV_FILE_PATTERN
+ else
+ @packet_log_frame.output_filename_filter = Cosmos::TXT_FILE_PATTERN
+ end
+ end
+ box.dispose
+ end
+
+ def batch_mode_changed
+ if @batch_mode_check.checked?
+ @config_box.hide
+ @batch_config_box.show
+ @open_button.setEnabled(false)
+ @open_excel_button.setEnabled(false) if Kernel.is_windows?
+ @fill_down_check.setEnabled(false)
+ @matlab_header_check.setEnabled(false)
+ @share_columns_check.setEnabled(false)
+ @unique_only_check.setEnabled(false)
+ @open_config.setEnabled(false)
+ @save_config.setEnabled(false)
+ @file_options.setEnabled(false)
+ @item_edit.setEnabled(false)
+ @item_delete.setEnabled(false)
+ @packet_log_frame.select_output_dir
+ @packet_log_frame.output_filename = @log_dir
+ else
+ @config_box.show
+ @batch_config_box.hide
+ @fill_down_check.setEnabled(true)
+ @matlab_header_check.setEnabled(true)
+ @share_columns_check.setEnabled(true)
+ @unique_only_check.setEnabled(true)
+ @open_config.setEnabled(true)
+ @save_config.setEnabled(true)
+ @file_options.setEnabled(true)
+ @item_edit.setEnabled(true)
+ @item_delete.setEnabled(true)
+ @packet_log_frame.select_output_file
+ @packet_log_frame.output_filename = ''
+ end
+ end
+
+ ###############################################################################
+ # Item Menu Handlers
+ ###############################################################################
+
+ def item_edit
+ item_list_editor()
+ end
+
+ def item_delete
+ @config_item_list.remove_selected_items
+ end
+
+ ###############################################################################
+ # Handlers
+ ###############################################################################
+
+ def handle_save_button
+ filename = nil
+ begin
+ if @config_field.text.strip.length > 0
+ filename = Qt::FileDialog.getSaveFileName(self, "Save Config File", @config_field.text, "Config Files (*.txt);;All Files (*)")
+ else
+ filename = Qt::FileDialog.getSaveFileName(self, "Save Config File", @config_dir, "Config Files (*.txt);;All Files (*)")
+ end
+ if filename and filename.length != 0
+ sync_gui_to_config()
+ @tlm_extractor_config.save(filename)
+ @config_field.setText(filename)
+ @config_dir = File.dirname(filename) + '/'
+ end
+ rescue => error
+ Qt::MessageBox.critical(self, 'Error!', "Error Saving Configuration File: #{filename}\n#{error.formatted}")
+ end
+ end
+
+ def handle_browse_button
+ filename = Qt::FileDialog::getOpenFileName(self, "Open Config File:", @config_dir, "Config Files (*.txt);;All Files (*)")
+ if filename and not filename.empty?
+ @config_field.setText(filename)
+ @config_dir = File.dirname(filename) + '/'
+
+ begin
+ process_config_file(filename)
+ rescue => error
+ Qt::MessageBox.critical(self, 'Error', "Error Processing Configuration File: #{filename}\n\n#{error.formatted}")
+ end
+ end
+ end
+
+ def item_list_editor
+ done_items = false
+ selected_items = @config_item_list.selected_items()
+ if @config_item_list.currentItem and !selected_items.empty?
+ selected_items.each do |item_index|
+ dialog = Qt::Dialog.new(self)
+ dialog.setWindowTitle("Edit Item")
+ layout = Qt::VBoxLayout.new
+ split_item = @config_item_list.item(item_index).text.scan ConfigParser::PARSING_REGEX
+
+ if split_item[0] == 'ITEM' and !done_items
+ label = Qt::Label.new("#{split_item[1]} #{split_item[2]} #{split_item[3]}")
+ layout.addWidget(label)
+
+ label = Qt::Label.new('Value Type:')
+ layout.addWidget(label)
+
+ box = Qt::ComboBox.new
+ box.maxCount = FORMATTING_OPTIONS.length
+ FORMATTING_OPTIONS.each {|item| box.addItem(item) }
+ current_formatting = split_item[4]
+ current_formatting = 'CONVERTED' unless current_formatting
+ if FORMATTING_OPTIONS.index(current_formatting)
+ box.currentIndex = FORMATTING_OPTIONS.index(current_formatting)
+ end
+ layout.addWidget(box)
+
+ check_box = nil
+ if selected_items.length > 1 and item_index == selected_items[0]
+ check_box = Qt::CheckBox.new('Apply to All?')
+ layout.addWidget(check_box)
+ end
+
+ button_layout = Qt::BoxLayout.new(Qt::Horizontal)
+ ok = Qt::PushButton.new("Save")
+ connect(ok, SIGNAL('clicked()'), dialog, SLOT('accept()'))
+ button_layout.addWidget(ok)
+ cancel = Qt::PushButton.new("Cancel")
+ connect(cancel, SIGNAL('clicked()'), dialog, SLOT('reject()'))
+ button_layout.addWidget(cancel)
+ layout.addLayout(button_layout)
+
+ dialog.setLayout(layout)
+ if dialog.exec == Qt::Dialog::Accepted
+ if box.currentIndex != -1
+ set_indexes = [item_index]
+ if check_box and check_box.checked?
+ set_indexes = selected_items
+ end
+
+ set_indexes.each do |set_item_index|
+ split_item = @config_item_list.item(set_item_index).text.scan ConfigParser::PARSING_REGEX
+ if split_item[0] == 'ITEM'
+ # Remove any formatting from the item by only keeping the first four strings
+ @config_item_list.item(set_item_index).text = split_item[0..3].join(' ')
+
+ if box.currentIndex != 0
+ @config_item_list.item(set_item_index).text = "#{@config_item_list.item(set_item_index).text} #{FORMATTING_OPTIONS[box.currentIndex]}"
+ end
+ end
+ end
+ if set_indexes.length > 1
+ done_items = true
+ end
+ end
+ end
+ elsif split_item[0] == 'TEXT'
+ label = Qt::Label.new('Column Name:')
+ layout.addWidget(label)
+ column_name = Qt::LineEdit.new(split_item[1].remove_quotes)
+ layout.addWidget(column_name)
+ label = Qt::Label.new('Text:')
+ layout.addWidget(label)
+ text = Qt::LineEdit.new(split_item[2].remove_quotes)
+ layout.addWidget(text)
+
+ button_layout = Qt::BoxLayout.new(Qt::Horizontal)
+ ok = Qt::PushButton.new("Save")
+ connect(ok, SIGNAL('clicked()'), dialog, SLOT('accept()'))
+ button_layout.addWidget(ok)
+ cancel = Qt::PushButton.new("Cancel")
+ connect(cancel, SIGNAL('clicked()'), dialog, SLOT('reject()'))
+ button_layout.addWidget(cancel)
+ layout.addLayout(button_layout)
+
+ dialog.setLayout(layout)
+ if dialog.exec == Qt::Dialog::Accepted
+ # Remove any formatting from the item by only keeping the first four strings
+ @config_item_list.item(item_index).text = "#{split_item[0]} \"#{column_name.text}\" \"#{text.text}\""
+ end
+ end
+
+ dialog.dispose
+ end
+ end
+ end
+
+ def open_button
+ Cosmos.open_in_text_editor(@packet_log_frame.output_filename)
+ end
+
+ def open_excel_button
+ system("start Excel.exe \"#{@packet_log_frame.output_filename}\"")
+ end
+
+ def clear_config_item_list
+ @config_item_list.clearItems
+ end # def clear_data_object_list
+
+ # Handles removing a selected filename
+ def handle_batch_remove_button
+ @batch_filenames_entry.remove_selected_items
+ end
+
+ # Handles browsing for log files
+ def handle_batch_browse_button
+ Cosmos.set_working_dir do
+ filenames = Qt::FileDialog::getOpenFileNames(
+ self, "Select Config File(s):", @config_dir, Cosmos::TXT_FILE_PATTERN)
+ if filenames and not filenames.empty?
+ @config_dir = File.dirname(filenames[0]) + '/'
+ filenames.each {|filename| @batch_filenames_entry.addItem(filename) if @batch_filenames_entry.findItems(filename, Qt::MatchExactly).empty? }
+ end
+ end
+ end
+
+ ###############################################################################
+ # Additional Callbacks
+ ###############################################################################
+
+ def cancel_callback(progress_dialog = nil)
+ @cancel = true
+ return true, false
+ end
+
+ def add_target_callback(target_name)
+ packets = System.telemetry.packets(target_name)
+ packets.each do |packet_name, packet|
+ packet.sorted_items.each do |item|
+ @config_item_list.addItem("ITEM #{target_name} #{packet_name} #{item.name}")
+ end
+ end
+ end
+
+ def add_packet_callback(target_name, packet_name)
+ packet = System.telemetry.packet(target_name, packet_name)
+ packet.sorted_items.each do |item|
+ @config_item_list.addItem("ITEM #{target_name} #{packet_name} #{item.name}")
+ end
+ end
+
+ def add_item_callback(target_name, packet_name, item_name)
+ @config_item_list.addItem("ITEM #{target_name} #{packet_name} #{item_name}")
+ end
+
+ def add_text_item_callback(column_name, text)
+ @config_item_list.addItem("TEXT \"#{column_name}\" \"#{text}\"")
+ end
+
+ ###############################################################################
+ # Helper Methods
+ ###############################################################################
+
+ def pre_process_tests
+ if !@batch_mode_check.checked?
+ # Normal Mode
+
+ if @config_item_list.count < 1
+ Qt::MessageBox.critical(self, 'Error', 'Please select at least 1 item')
+ return false
+ end
+
+ unless @packet_log_frame.output_filename
+ Qt::MessageBox.critical(self, 'Error', 'No Output File Selected')
+ return false
+ end
+
+ if File.exist?(@packet_log_frame.output_filename)
+ result = Qt::MessageBox.warning(self, 'Warning!', 'Output File Already Exists. Overwrite?', Qt::MessageBox::Yes | Qt::MessageBox::No)
+ return false if result == Qt::MessageBox::No
+ end
+ else
+ # Batch Mode
+
+ if !@batch_name_entry.text or @batch_name_entry.text.strip.empty?
+ Qt::MessageBox.critical(self, 'Error', 'Batch Name is Required')
+ return false
+ end
+
+ unless @batch_filenames and @batch_filenames[0]
+ Qt::MessageBox.critical(self, 'Error', 'Please select at least 1 config file')
+ return false
+ end
+ end
+
+ unless @input_filenames and @input_filenames[0]
+ Qt::MessageBox.critical(self, 'Error', 'Please select at least 1 input file')
+ return false
+ end
+
+ # Validate configurations exist for input filenames
+ @input_filenames.each do |input_filename|
+ Cosmos.check_log_configuration(@tlm_extractor_processor.packet_log_reader, input_filename)
+ end
+
+ #Validate config information
+ @tlm_extractor_processor.packet_log_reader.open(@input_filenames[0])
+ @tlm_extractor_processor.packet_log_reader.close
+
+ @config_item_list.each do |item|
+ split_item = item.text.split
+ item_type = split_item[0]
+ target_name = split_item[1]
+ packet_name = split_item[2]
+ item_name = split_item[3]
+
+ if item_type == 'ITEM'
+ # Verify Packet
+ packet = nil
+ begin
+ packet = System.telemetry.packet(target_name, packet_name)
+ rescue
+ Qt::MessageBox.critical(self, 'Error!', "Unknown Packet #{target_name} #{packet_name} specified")
+ return false
+ end
+
+ # Verify Item
+ begin
+ packet.get_item(item_name)
+ rescue
+ Qt::MessageBox.critical(self, 'Error!', "Item #{item_name} not present in packet")
+ return false
+ end
+ end
+ end
+
+ true
+ end
+
+ def process_config_file(filename)
+ @tlm_extractor_config.restore(filename)
+ sync_config_to_gui()
+ end
+
+ def change_callback(item_changed)
+ if item_changed == :INPUT_FILES
+ if !@batch_mode_check.checked?
+ filename = @packet_log_frame.filenames[0]
+ if filename
+ extension = File.extname(filename)
+ filename_no_extension = filename[0..-(extension.length + 1)]
+ if @packet_log_frame.output_filename_filter == Cosmos::CSV_FILE_PATTERN
+ filename = filename_no_extension << '.csv'
+ else
+ filename = filename_no_extension << '.txt'
+ end
+ @packet_log_frame.output_filename = filename
+ end
+ end
+ elsif item_changed == :OUTPUT_FILE
+ output_filename = @packet_log_frame.output_filename
+ if output_filename and !output_filename.to_s.strip.empty?
+ @log_dir = File.dirname(output_filename)
+ end
+ elsif item_changed == :OUTPUT_DIR
+ output_filename = @packet_log_frame.output_filename
+ if output_filename and !output_filename.to_s.strip.empty?
+ @log_dir = output_filename
+ end
+ end
+ end
+
+ end # class TlmExtractor
+
+end # module Cosmos