Class: Cosmos::LimitsMonitor

Inherits:
QtTool show all
Defined in:
lib/cosmos/tools/limits_monitor/limits_monitor.rb

Overview

The LimitsMonitor application displays all the out of limits items encountered by the COSMOS server. It provides the ability to ignore and restore limits as well as logs all limits events.

Defined Under Namespace

Classes: LimitsWidget

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from QtTool

#about, #complete_initialize, create_default_options, graceful_kill, #initialize_help_menu, post_options_parsed_hook, pre_window_new_hook, redirect_io, restore_io

Constructor Details

#initialize(options) ⇒ LimitsMonitor

Create the main application GUI. Start the limits thread which responds to asynchronous limits events from the server and the value thread which polls the server at 1Hz for the out of limits items values.

Parameters:

  • options (Options)

    Contains the options for the window.



409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 409

def initialize(options)
  super(options)
  Cosmos.load_cosmos_icon("limits_monitor.png")

  @cancel_thread = false
  @limits_sleeper = Sleeper.new
  @value_sleeper = Sleeper.new

  initialize_actions()
  initialize_menus()
  initialize_central_widget()
  complete_initialize()

  @limits_items = LimitsItems.new(
    method(:new_gui_item), method(:update_gui_item), method(:clear_gui_items))
  result = @limits_items.open_config(options.config_file)
  statusBar.showMessage(tr(result))

  limits_thread()
  value_thread()
end

Class Method Details

.run(option_parser = nil, options = nil) ⇒ Object

Initialize tool options.



814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 814

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 = 600
      options.height = 500
      options.remember_geometry = false
      options.title = "Limits Monitor"
      options.auto_size = false
      options.config_file = nil
      options.production = false
      options.no_prompt = false
      option_parser.separator "Limits Monitor Specific Options:"
      option_parser.on("-c", "--config FILE", "Use the specified configuration file") do |arg|
        options.config_file = arg
      end
    end

    super(option_parser, options)
  end
end

Instance Method Details

#clear_gui_itemsObject

Reset the GUI by clearing all items



698
699
700
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 698

def clear_gui_items
  Qt.execute_in_main_thread(true) { @scroll_layout.removeAll }
end

#closeEvent(event) ⇒ Object

Handle the window closing



798
799
800
801
802
803
804
805
806
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 798

def closeEvent(event)
  @cancel_thread = true
  @value_sleeper.cancel
  @limits_sleeper.cancel
  shutdown_cmd_tlm()
  Cosmos.kill_thread(self, @limits_thread, 2)
  Cosmos.kill_thread(self, @value_thread, 2)
  super(event)
end

#config_pathString

Returns Fully qualified path to the configuration file.

Returns:

  • (String)

    Fully qualified path to the configuration file



566
567
568
569
570
571
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 566

def config_path
  # If the config file has been set then just return it
  return @filename if @filename
  # This is the default path to the configuration files
  File.join(::Cosmos::USERPATH, 'config', 'tools', 'limits_monitor', 'limits_monitor.txt')
end

#edit_ignored_itemsObject

Opens a dialog to allow the user to remove ignored items



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 595

def edit_ignored_items
  items = []
  index = 0
  @limits_items.ignored.each do |target_name, packet_name, item_name|
    item = Qt::ListWidgetItem.new("#{target_name} #{packet_name} #{item_name}")
    item.setData(Qt::UserRole, Qt::Variant.new(@limits_items.ignored[index]))
    items << item
    index += 1
  end

  Qt::Dialog.new(self) do |dialog|
    dialog.setWindowTitle('Ignored Telemetry Items')
    list = Qt::ListWidget.new
    list.setFocus()
    # Allow multiple sections
    list.setSelectionMode(Qt::AbstractItemView::ExtendedSelection)
    items.each {|item| list.addItem(item) }

    shortcut = Qt::Shortcut.new(Qt::KeySequence.new(Qt::KeySequence::Delete), list)
    list.connect(shortcut, SIGNAL('activated()')) do
      items = list.selectedItems()
      (0...items.length).each do |index|
        @limits_items.remove_ignored(items[index].data(Qt::UserRole).value)
      end
      list.remove_selected_items
      list.setCurrentRow(0)
    end
    # Preselect the first row (works if list is empty) so the keyboard
    # works instantly without having to click the list
    list.setCurrentRow(0)

    ok = Qt::PushButton.new('Ok') do
      connect(SIGNAL('clicked()')) { dialog.done(0) }
    end
    remove = Qt::PushButton.new('Remove Selected') do
      connect(SIGNAL('clicked()')) { shortcut.activated() }
    end
    button_layout = Qt::HBoxLayout.new do
      addWidget(ok)
      addStretch(1)
      addWidget(remove)
    end
    dialog.layout = Qt::VBoxLayout.new do
      addWidget(list)
      addLayout(button_layout)
    end
    dialog.resize(500, 200)
    dialog.exec
    dialog.dispose
  end
end

#graceful_killObject

Gracefully kill threads



809
810
811
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 809

def graceful_kill
  Qt::CoreApplication.processEvents()
end

#ignore(widget, item) ⇒ Object

Update front panel to ignore an item when the corresponding button is pressed.

Parameters:

  • item (Array<String,String,String] Array containing the target name, packet name, and item name of the item to ignore.)

    tem [Array<String,String,String] Array containing the target name, packet name, and item name of the item to ignore.



706
707
708
709
710
711
712
713
714
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 706

def ignore(widget, item)
  @limits_items.ignore(item)
  Qt.execute_in_main_thread(true) do
    @scroll_layout.removeWidget(widget)
    widget.dispose
    @scroll_widget.adjustSize
    statusBar.showMessage('Warning: Some Telemetry Items are Ignored')
  end
end

#initialize_actionsObject

Initialize all the actions in the application Menu



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 432

def initialize_actions
  super

  @options_action = Qt::Action.new(tr('O&ptions'), self)
  @options_action.statusTip = tr('Open the options dialog')
  @options_action.connect(SIGNAL('triggered()')) { show_options_dialog() }

  @reset_action = Qt::Action.new(tr('&Reset'), self)
  @reset_action_keyseq = Qt::KeySequence.new(tr('Ctrl+R'))
  @reset_action.shortcut = @reset_action_keyseq
  @reset_action.statusTip = tr('Reset connection and clear all items. This does not modify the ignored items.')
  @reset_action.connect(SIGNAL('triggered()')) { @limits_items.request_reset() }

  @open_ignored_action = Qt::Action.new(Cosmos.get_icon('open.png'),
                                        tr('&Open Config'), self)
  @open_ignored_action_keyseq = Qt::KeySequence.new(tr('Ctrl+O'))
  @open_ignored_action.shortcut = @open_ignored_action_keyseq
  @open_ignored_action.statusTip = tr('Open ignored telemetry items configuration file')
  @open_ignored_action.connect(SIGNAL('triggered()')) { open_config_file() }

  @save_ignored_action = Qt::Action.new(Cosmos.get_icon('save.png'),
                                        tr('&Save Config'), self)
  @save_ignored_action_keyseq = Qt::KeySequence.new(tr('Ctrl+S'))
  @save_ignored_action.shortcut = @save_ignored_action_keyseq
  @save_ignored_action.statusTip = tr('Save all ignored telemetry items in a configuration file')
  @save_ignored_action.connect(SIGNAL('triggered()')) { save_config_file() }

  @edit_ignored_action = Qt::Action.new(tr('&Edit Ignored'), self)
  @edit_ignored_action_keyseq = Qt::KeySequence.new(tr('Ctrl+E'))
  @edit_ignored_action.shortcut = @edit_ignored_action_keyseq
  @edit_ignored_action.statusTip = tr('Edit the ignored telemetry items list')
  @edit_ignored_action.connect(SIGNAL('triggered()')) { edit_ignored_items() }
end

#initialize_central_widgetObject

Layout the main GUI tab widget with a view of all the out of limits items in one tab and a log tab showing all limits events.



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 486

def initialize_central_widget
  @tabbook = Qt::TabWidget.new(self)
  setCentralWidget(@tabbook)
  @widget = Qt::Widget.new
  @layout = Qt::VBoxLayout.new(@widget)

  @monitored_state_text_field = Qt::LineEdit.new(self)
  @monitored_state_text_field.setText('Stale')
  @monitored_state_text_field.setAlignment(Qt::AlignCenter)
  @monitored_state_text_field.setReadOnly(true)
  @palette = Qt::Palette.new()
  @palette.setColor(Qt::Palette::Base, Qt::Color.new(255,0,255))
  @monitored_state_text_field.setPalette(@palette)
  @state_label = Qt::Label.new('Monitored Limits State: ')

  @monitored_state_frame = Qt::HBoxLayout.new
  @monitored_state_frame.addWidget(@state_label)
  @monitored_state_frame.addWidget(@monitored_state_text_field)
  label = Qt::Label.new
  filename = File.join(::Cosmos::PATH, 'data', 'spinner.gif')
  movie = Qt::Movie.new(filename)
  label.setMovie(movie)
  movie.start
  @monitored_state_frame.addWidget(label)
  @monitored_state_frame.setAlignment(Qt::AlignTop)
  @layout.addLayout(@monitored_state_frame)

  @scroll = Qt::ScrollArea.new
  @scroll_widget = Qt::Widget.new
  @scroll.setWidget(@scroll_widget)
  @scroll_layout = Qt::VBoxLayout.new(@scroll_widget)
  @scroll_layout.setSizeConstraint(Qt::Layout::SetMinAndMaxSize)
  @layout.addWidget(@scroll)

  @log_output = Qt::PlainTextEdit.new
  @log_output.setReadOnly(true)
  @log_output.setMaximumBlockCount(100)

  @tabbook.addTab(@widget, "Limits")
  @tabbook.addTab(@log_output, "Log")
end

#initialize_menusObject

Initialize the application menu bar options



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 467

def initialize_menus
  @file_menu = menuBar.addMenu(tr('&File'))
  @file_menu.addAction(@open_ignored_action)
  @file_menu.addAction(@save_ignored_action)
  @file_menu.addAction(@edit_ignored_action)
  @file_menu.addSeparator()
  @file_menu.addAction(@reset_action)
  @file_menu.addAction(@options_action)
  @file_menu.addSeparator()
  @file_menu.addAction(@exit_action)

  # Help Menu
  @about_string = "Limits Monitor displays all telemetry items that are or have been out of limits since it was started or reset."

  initialize_help_menu()
end

#limits_threadObject

Thread to monitor for broken limits and add them to the log and front panel when found.



649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 649

def limits_thread
  result = nil
  color = nil
  @limits_thread = Thread.new do
    while true
      break if @cancel_thread
      Qt.execute_in_main_thread(true) do
        result, color = @limits_items.process_events()
      end
      if result
        update_log(result, color)
      else
        break if @limits_sleeper.sleep(1)
      end
    end
  end
rescue Exception => error
  Cosmos.handle_fatal_exception(error)
end

#new_gui_item(target_name, packet_name, item_name) ⇒ Qt::Widget

Add new out of limit item or stale packet

Parameters:

  • target_name (String)

    Target name of out of limits item.

  • packet_name (String)

    Packet name of out of limits item.

  • item_name (String)

    Item name of out of limits item or nil if its a stale packet

Returns:

  • (Qt::Widget)

    The new widget that was created



676
677
678
679
680
681
682
683
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 676

def new_gui_item(target_name, packet_name, item_name)
  widget = nil
  Qt.execute_in_main_thread(true) do
    widget = LimitsWidget.new(self, target_name, packet_name, item_name)
    @scroll_layout.addWidget(widget)
  end
  widget
end

#open_config_fileObject

Opens the configuration file and loads the ignored items



574
575
576
577
578
579
580
581
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 574

def open_config_file
  filename = Qt::FileDialog::getOpenFileName(self,
    "Open Configuration File", config_path())
  unless filename.nil? || filename.empty?
    result = @limits_items.open_config(filename)
    statusBar.showMessage(tr(result))
  end
end

#save_config_fileObject

Saves the ignored items to the configuration file



584
585
586
587
588
589
590
591
592
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 584

def save_config_file
  filename = Qt::FileDialog.getSaveFileName(self,
    'Save As...', config_path(), 'Configuration Files (*.txt)')
  unless filename.nil? || filename.empty?
    result = @limits_items.save_config(filename)
    statusBar.showMessage(tr(result))
    @filename = filename
  end
end

#show_options_dialogObject



528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 528

def show_options_dialog
  Qt::Dialog.new(self) do |dialog|
    dialog.setWindowTitle('Options')

    colorblind_box = Qt::CheckBox.new('Colorblind Mode Enabled', self)
    colorblind_box.setCheckState(Qt::Checked) if @colorblind

    ok = Qt::PushButton.new('Ok') do
      connect(SIGNAL('clicked()')) { dialog.accept }
    end
    cancel = Qt::PushButton.new('Cancel') do
      connect(SIGNAL('clicked()')) { dialog.reject }
    end
    buttons = Qt::HBoxLayout.new do
      addWidget(ok)
      addWidget(cancel)
    end
    dialog.layout = Qt::VBoxLayout.new do
      addWidget(colorblind_box)
      addLayout(buttons)
    end

    case dialog.exec
    when Qt::Dialog::Accepted
      if (colorblind_box.checkState() == Qt::Checked)
        @colorblind = true
      else
        @colorblind = false
      end
      (0...@scroll_layout.count).each do |index|
        @scroll_layout.itemAt(index).widget.set_colorblind(@colorblind)
      end
    end
    dialog.dispose
  end
end

#update_gui_item(widget, value, limits_state, limits_set) ⇒ Object

Update out of limit item with a values

Parameters:

  • target_name (String)

    Target name of out of limits item.

  • packet_name (String)

    Packet name of out of limits item.

  • item_name (String)

    Item name of out of limits item or nil if its a stale packet



691
692
693
694
695
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 691

def update_gui_item(widget, value, limits_state, limits_set)
  Qt.execute_in_main_thread(true) do
    widget.set_values(value, limits_state, limits_set) if widget
  end
end

#update_log(message, color) ⇒ Object

Update the log panel with limits change information.

Parameters:

  • message (String)

    Text string with information about which item is out of limits, what its value is, and what limit was broken (red_low, yellow_low, etc.)

  • color (Symbol)

    Color of text to add.



721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 721

def update_log(message, color)
  return if @cancel_thread
  Qt.execute_in_main_thread(true) do
    @tf ||= Qt::TextCharFormat.new
    case color
    when :GREEN
      brush = Cosmos.getBrush(Cosmos::GREEN)
    when :YELLOW
      brush = Cosmos.getBrush(Cosmos::YELLOW)
    when :RED
      brush = Cosmos.getBrush(Cosmos::RED)
    when :BLUE
      brush = Cosmos.getBrush(Cosmos::BLUE)
    else # :BLACK
      brush = Cosmos.getBrush(Cosmos::BLACK)
    end
    @tf.setForeground(brush)
    @log_output.setCurrentCharFormat(@tf)
    @log_output.appendPlainText(message.chomp)
  end
end

#update_overall_limits_state(state) ⇒ Object

Changes the limits state on the status bar at the top of the screen.



767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 767

def update_overall_limits_state(state)
  Qt.execute_in_main_thread(true) do
    text = ''
    case state
    when :STALE
      palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,0,255))
      @monitored_state_text_field.setPalette(palette)
      text = 'Stale'
    when :GREEN, :GREEN_HIGH, :GREEN_LOW
      palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(0,255,0))
      @monitored_state_text_field.setPalette(palette)
      text = 'Green'
    when :YELLOW, :YELLOW_HIGH, :YELLOW_LOW
      palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,255,0))
      @monitored_state_text_field.setPalette(palette)
      text = 'Yellow'
    when :RED, :RED_HIGH, :RED_LOW
      palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(255,0,0))
      @monitored_state_text_field.setPalette(palette)
      text = 'Red'
    when :BLUE
      palette = Cosmos.getPalette(Cosmos.getColor(0, 0, 0), Cosmos.getColor(0,0,255))
      @monitored_state_text_field.setPalette(palette)
      text = 'Blue'
    end
    text << ' - Some Items Ignored' if @limits_items.ignored_items?
    @monitored_state_text_field.text = text
  end
end

#value_threadObject

Thread to request the out of limits values and update them at 1Hz. Also updates the status bar at the top of the front panel indicating the overall limits value of the system.



746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/cosmos/tools/limits_monitor/limits_monitor.rb', line 746

def value_thread
  @value_thread = Thread.new do
    while true
      break if @cancel_thread
      Qt.execute_in_main_thread(true) do
        if @limits_items.initialized
          @limits_items.update_values()
          update_overall_limits_state(@limits_items.overall_state())
        else
          # Set the status bar message to expire in 2s since this runs at 1Hz
          statusBar.showMessage('Error Connecting to Command and Telemetry Server', 2000)
        end
      end
      break if @value_sleeper.sleep(1)
    end
  end
rescue Exception => error
  Cosmos.handle_fatal_exception(error)
end