#!@PYTHON3@ # -*- coding: utf-8 -*- # 2009-12-27 David Goncalves - Version 1.2 # Total rewrite of NUT-Monitor to optimize GUI interaction. # Added favorites support (saved to user's home) # Added status icon on the notification area # # 2010-02-26 David Goncalves # Added UPS vars display and the possibility to change values # when user double-clicks on a RW var. # # 2010-05-01 David Goncalves # Added support for PyNotify (if available) # # 2010-05-05 David Goncalves # Added support for command line options # -> --start-hidden # -> --favorite # # NUT-Monitor now tries to detect if there is a NUT server # on localhost and if there is 1 UPS, connects to it. # # 2010-10-06 David Goncalves - Version 1.3 # Added localisation support # # 2015-02-14 Michal Fincham - Version 1.3.1 # Corrected unsafe permissions on ~/.nut-monitor (Debian #777706) # # 2022-02-20 Luke Dashjr - Version 2.0 # Port to Python 3 with PyQt5. import PyQt5.uic from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import sys import base64 import os, os.path import stat import platform import time import threading import optparse import configparser import locale import gettext import PyNUT class interface : DESIRED_FAVORITES_DIRECTORY_MODE = 0o700 __widgets = {} __callbacks = {} __favorites = {} __favorites_file = None __favorites_path = "" __fav_menu_items = list() __window_visible = True __ui_file = None __connected = False __ups_handler = None __ups_commands = None __ups_vars = None __ups_rw_vars = None __gui_thread = None __current_ups = None def __init__( self, argv ) : # Before anything, parse command line options if any present... opt_parser = optparse.OptionParser() opt_parser.add_option( "-H", "--start-hidden", action="store_true", default=False, dest="hidden", help="Start iconified in tray" ) opt_parser.add_option( "-F", "--favorite", dest="favorite", help="Load the specified favorite and connect to UPS" ) ( cmd_opts, args ) = opt_parser.parse_args() self.__app = QApplication( argv ) self.__ui_file = self.__find_res_file( 'ui', "window1.ui" ) self.__widgets["interface"] = PyQt5.uic.loadUi( self.__ui_file ) self.__widgets["main_window"] = self.__widgets["interface"] self.__widgets["status_bar"] = self.__widgets["interface"].statusbar2 self.__widgets["ups_host_entry"] = self.__widgets["interface"].entry1 self.__widgets["ups_port_entry"] = self.__widgets["interface"].spinbutton1 self.__widgets["ups_refresh_button"] = self.__widgets["interface"].button1 self.__widgets["ups_authentication_check"] = self.__widgets["interface"].checkbutton1 self.__widgets["ups_authentication_frame"] = self.__widgets["interface"].hbox1 self.__widgets["ups_authentication_login"] = self.__widgets["interface"].entry2 self.__widgets["ups_authentication_password"] = self.__widgets["interface"].entry3 self.__widgets["ups_list_combo"] = self.__widgets["interface"].combobox1 self.__widgets["ups_commands_combo"] = self.__widgets["interface"].ups_commands_combo self.__widgets["ups_commands_button"] = self.__widgets["interface"].button8 self.__widgets["ups_connect"] = self.__widgets["interface"].button2 self.__widgets["ups_disconnect"] = self.__widgets["interface"].button7 self.__widgets["ups_params_box"] = self.__widgets["interface"].vbox6 self.__widgets["ups_infos"] = self.__widgets["interface"].notebook1 self.__widgets["ups_vars_tree"] = self.__widgets["interface"].treeview1 self.__widgets["ups_vars_refresh"] = self.__widgets["interface"].button9 self.__widgets["ups_status_image"] = self.__widgets["interface"].image1 self.__widgets["ups_status_left"] = self.__widgets["interface"].label10 self.__widgets["ups_status_right"] = self.__widgets["interface"].label11 self.__widgets["ups_status_time"] = self.__widgets["interface"].label15 self.__widgets["menu_favorites_root"] = self.__widgets["interface"].menu2 self.__widgets["menu_favorites"] = self.__widgets["interface"].menu2 self.__widgets["menu_favorites_add"] = self.__widgets["interface"].menuitem4 self.__widgets["menu_favorites_del"] = self.__widgets["interface"].menuitem5 self.__widgets["progress_battery_charge"] = self.__widgets["interface"].progressbar1 self.__widgets["progress_battery_load"] = self.__widgets["interface"].progressbar2 # Create the tray icon and connect it to the show/hide method... self.__widgets["status_icon"] = QSystemTrayIcon( QIcon( self.__find_res_file( "pixmaps", "on_line.png" ) ) ) self.__widgets["status_icon"].setVisible( True ) self.__widgets["status_icon"].activated.connect( self.tray_activated ) self.__widgets["ups_status_image"].setPixmap( QPixmap( self.__find_res_file( "pixmaps", "on_line.png" ) ) ) # Connect interface callbacks actions self.__widgets["main_window"].destroyed.connect( self.quit ) self.__widgets["interface"].imagemenuitem1.triggered.connect( self.gui_about_dialog ) self.__widgets["interface"].imagemenuitem5.triggered.connect( self.quit ) self.__widgets["ups_host_entry"].textChanged.connect( self.__check_gui_fields ) self.__widgets["ups_authentication_login"].textChanged.connect( self.__check_gui_fields ) self.__widgets["ups_authentication_password"].textChanged.connect( self.__check_gui_fields ) self.__widgets["ups_authentication_check"].stateChanged.connect( self.__check_gui_fields ) self.__widgets["ups_port_entry"].valueChanged.connect( self.__check_gui_fields ) self.__widgets["ups_refresh_button"].clicked.connect( self.__update_ups_list ) self.__widgets["ups_connect"].clicked.connect( self.connect_to_ups ) self.__widgets["ups_disconnect"].clicked.connect( self.disconnect_from_ups ) self.__widgets["ups_vars_refresh"].clicked.connect( self.__gui_update_ups_vars_view ) self.__widgets["menu_favorites_add"].triggered.connect( self.__gui_add_favorite ) self.__widgets["menu_favorites_del"].triggered.connect( self.__gui_delete_favorite ) self.__widgets["ups_vars_tree"].doubleClicked.connect( self.__gui_ups_vars_selected ) # Remove the dummy combobox entry on UPS List and Commands self.__widgets["ups_list_combo"].removeItem( 0 ) # Set UPS vars treeview properties ----------------------------- store = QStandardItemModel( 0, 3, self.__widgets["ups_vars_tree"] ) self.__widgets["ups_vars_tree"].setModel( store ) self.__widgets["ups_vars_tree"].setHeaderHidden( False ) self.__widgets["ups_vars_tree"].setRootIsDecorated( False ) # Column 0 store.setHeaderData( 0, Qt.Horizontal, '' ) # Column 1 store.setHeaderData( 1, Qt.Horizontal, _('Var name') ) # Column 2 store.setHeaderData( 2, Qt.Horizontal, _('Value') ) self.__widgets["ups_vars_tree"].header().setStretchLastSection( True ) self.__widgets["ups_vars_tree"].sortByColumn( 1, Qt.AscendingOrder ) self.__widgets["ups_vars_tree_store"] = store self.__widgets["ups_vars_tree"].setMinimumSize( 0, 50 ) #--------------------------------------------------------------- # UPS Commands combo box creation ------------------------------ ups_commands_height = self.__widgets["ups_commands_combo"].size().height() * 2 self.__widgets["ups_commands_combo"].setMinimumSize(0, ups_commands_height) self.__widgets["ups_commands_combo"].setCurrentIndex( 0 ) self.__widgets["ups_commands_button"].setMinimumSize(0, ups_commands_height) self.__widgets["ups_commands_button"].clicked.connect( self.__gui_send_ups_command ) self.__widgets["ups_commands_combo_store"] = self.__widgets["ups_commands_combo"] #--------------------------------------------------------------- self.gui_init_unconnected() if ( cmd_opts.hidden != True ) : self.__widgets["main_window"].show() # Define favorites path and load favorites if ( platform.system() == "Linux" ) : self.__favorites_path = os.path.join( os.environ.get("HOME"), ".nut-monitor" ) elif ( platform.system() == "Windows" ) : self.__favorites_path = os.path.join( os.environ.get("USERPROFILE"), "Application Data", "NUT-Monitor" ) self.__favorites_file = os.path.join( self.__favorites_path, "favorites.ini" ) self.__parse_favorites() self.gui_status_message( _("Welcome to NUT Monitor") ) if ( cmd_opts.favorite != None ) : if ( cmd_opts.favorite in self.__favorites ) : self.__gui_load_favorite( fav_name=cmd_opts.favorite ) self.connect_to_ups() else : # Try to scan localhost for available ups and connect to it if there is only one self.__widgets["ups_host_entry"].setText( "localhost" ) self.__update_ups_list() if self.__widgets["ups_list_combo"].count() == 1: self.connect_to_ups() def exec( self ) : self.__app.exec() def __find_res_file( self, ftype, filename ) : filename = os.path.join( ftype, filename ) # TODO: Skip checking application directory if installed path = os.path.join( os.path.dirname( sys.argv[0] ), filename ) if os.path.exists(path): return path path = QStandardPaths.locate(QStandardPaths.AppDataLocation, filename) if os.path.exists(path): return path raise RuntimeError("Cannot find %s resource %s" % (ftype, filename)) def __find_icon_file( self ) : filename = 'nut-monitor.png' # TODO: Skip checking application directory if installed path = os.path.join( os.path.dirname( sys.argv[0] ), "icons", "256x256", filename ) if os.path.exists(path): return path path = QStandardPaths.locate(QStandardPaths.AppDataLocation, os.path.join( "icons", "hicolor", "256x256", "apps", filename ) ) if os.path.exists(path): return path raise RuntimeError("Cannot find %s resource %s" % ('icon', filename)) # Check if correct fields are filled to enable connection to the UPS def __check_gui_fields( self, widget=None ) : # If UPS list contains something, clear it if self.__widgets["ups_list_combo"].currentIndex() != -1 : self.__widgets["ups_list_combo"].clear() self.__widgets["ups_connect"].setEnabled( False ) self.__widgets["menu_favorites_add"].setEnabled( False ) # Host/Port selection if len( self.__widgets["ups_host_entry"].text() ) > 0 : sensitive = True # If authentication is selected, check that we have a login and password if self.__widgets["ups_authentication_check"].isChecked() : if len( self.__widgets["ups_authentication_login"].text() ) == 0 : sensitive = False if len( self.__widgets["ups_authentication_password"].text() ) == 0 : sensitive = False self.__widgets["ups_refresh_button"].setEnabled( sensitive ) if not sensitive : self.__widgets["ups_connect"].setEnabled( False ) self.__widgets["menu_favorites_add"].setEnabled( False ) else : self.__widgets["ups_refresh_button"].setEnabled( False ) self.__widgets["ups_connect"].setEnabled( False ) self.__widgets["menu_favorites_add"].setEnabled( False ) # Use authentication fields... if self.__widgets["ups_authentication_check"].isChecked() : self.__widgets["ups_authentication_frame"].setEnabled( True ) else : self.__widgets["ups_authentication_frame"].setEnabled( False ) self.gui_status_message() #------------------------------------------------------------------- # This method is used to show/hide the main window when user clicks on the tray icon def tray_activated( self, widget=None, data=None ) : if self.__window_visible : self.__widgets["main_window"].hide() else : self.__widgets["main_window"].show() self.__window_visible = not self.__window_visible #------------------------------------------------------------------- # Change the status icon and tray icon def change_status_icon( self, icon="on_line", blink=False ) : self.__widgets["status_icon"].setIcon( QIcon( self.__find_res_file( "pixmaps", "%s.png" % icon ) ) ) self.__widgets["ups_status_image"].setPixmap( QPixmap( self.__find_res_file( "pixmaps", "%s.png" % icon ) ) ) # TODO self.__widgets["status_icon"].set_blinking( blink ) #------------------------------------------------------------------- # This method connects to the NUT server and retrieve availables UPSes # using connection parameters (host, port, login, pass...) def __update_ups_list( self, widget=None ) : host = self.__widgets["ups_host_entry"].text() port = int( self.__widgets["ups_port_entry"].value() ) login = None password = None if self.__widgets["ups_authentication_check"].isChecked() : login = self.__widgets["ups_authentication_login"].text() password = self.__widgets["ups_authentication_password"].text() try : nut_handler = PyNUT.PyNUTClient( host=host, port=port, login=login, password=password ) upses = nut_handler.GetUPSList() ups_list = list(key.decode('ascii') for key in upses.keys()) ups_list.sort() # If UPS list contains something, clear it self.__widgets["ups_list_combo"].clear() for current in ups_list : self.__widgets["ups_list_combo"].addItem( current ) self.__widgets["ups_list_combo"].setCurrentIndex( 0 ) self.__widgets["ups_connect"].setEnabled( True ) self.__widgets["menu_favorites_add"].setEnabled( True ) self.gui_status_message( _("Found {0} devices on {1}").format( len( ups_list ), host ) ) except : error_msg = _("Error connecting to '{0}' ({1})").format( host, sys.exc_info()[1] ) self.gui_status_message( error_msg ) #------------------------------------------------------------------- # Quit program def quit( self, widget=None ) : # If we are connected to an UPS, disconnect first... if self.__connected : self.gui_status_message( _("Disconnecting from device") ) self.disconnect_from_ups() self.__app.quit() #------------------------------------------------------------------- # Method called when user wants to add a new favorite entry. It # displays a dialog to enable user to select the name of the favorite def __gui_add_favorite( self, widget=None ) : dialog_ui_file = self.__find_res_file( 'ui', "dialog1.ui" ) dialog = PyQt5.uic.loadUi( dialog_ui_file ) # Define interface callbacks actions def check_entry(val): if self.__gui_add_favorite_check_gui_fields(val): dialog.buttonBox.button(QDialogButtonBox.Ok).setEnabled( True ) else: dialog.buttonBox.button(QDialogButtonBox.Ok).setEnabled( False ) dialog.entry4.textChanged.connect( check_entry ) self.__widgets["main_window"].setEnabled( False ) rc = dialog.exec() if rc == QDialog.Accepted : fav_data = {} fav_data["host"] = self.__widgets["ups_host_entry"].text() fav_data["port"] = "%d" % self.__widgets["ups_port_entry"].value() fav_data["ups"] = self.__widgets["ups_list_combo"].currentText() fav_data["auth"] = self.__widgets["ups_authentication_check"].isChecked() if fav_data["auth"] : fav_data["login"] = self.__widgets["ups_authentication_login"].text() fav_data["password"] = base64.b64encode( self.__widgets["ups_authentication_password"].text().encode('ascii') ).decode('ascii') fav_name = dialog.entry4.text() self.__favorites[ fav_name ] = fav_data self.__gui_refresh_favorites_menu() # Save all favorites self.__save_favorites() self.__widgets["main_window"].setEnabled( True ) #------------------------------------------------------------------- # Method called when user wants to delete an entry from favorites def __gui_delete_favorite( self, widget=None ) : dialog_ui_file = self.__find_res_file( 'ui', "dialog2.ui" ) dialog = PyQt5.uic.loadUi( dialog_ui_file ) # Remove the dummy combobox entry on list dialog.combobox2.removeItem( 0 ) favs = list(self.__favorites.keys()) favs.sort() for current in favs : dialog.combobox2.addItem( current ) dialog.combobox2.setCurrentIndex( 0 ) self.__widgets["main_window"].setEnabled( False ) rc = dialog.exec() fav_name = dialog.combobox2.currentText() self.__widgets["main_window"].setEnabled( True ) if ( rc == QDialog.Accepted ) : # Remove entry, show confirmation dialog resp = QMessageBox.question( None, self.__widgets["main_window"].windowTitle(), _("Are you sure that you want to remove this favorite ?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) if ( resp == QMessageBox.Yes ) : del self.__favorites[ fav_name ] self.__gui_refresh_favorites_menu() self.__save_favorites() self.gui_status_message( _("Removed favorite '%s'") % fav_name ) #------------------------------------------------------------------- # Method called when user selects a favorite from the favorites menu def __gui_load_favorite( self, fav_name="" ) : if ( fav_name in self.__favorites ) : # If auth is activated, process it before other fields to avoir weird # reactions with the 'check_gui_fields' function. if ( self.__favorites[fav_name].get("auth", False ) ) : self.__widgets["ups_authentication_check"].setChecked( True ) self.__widgets["ups_authentication_login"].setText( self.__favorites[fav_name].get("login","") ) self.__widgets["ups_authentication_password"].setText( self.__favorites[fav_name].get("password","") ) self.__widgets["ups_host_entry"].setText( self.__favorites[fav_name].get("host","") ) self.__widgets["ups_port_entry"].setValue( int( self.__favorites[fav_name].get( "port", 3493 ) ) ) # Clear UPS list and add current UPS name self.__widgets["ups_list_combo"].clear() self.__widgets["ups_list_combo"].addItem( self.__favorites[fav_name].get("ups","") ) self.__widgets["ups_list_combo"].setCurrentIndex( 0 ) # Activate the connect button self.__widgets["ups_connect"].setEnabled( True ) self.gui_status_message( _("Loaded '%s'") % fav_name ) #------------------------------------------------------------------- # Send the selected command to the UPS def __gui_send_ups_command( self, widget=None ) : offset = self.__widgets["ups_commands_combo"].currentIndex() cmd = self.__ups_commands[ offset ].decode('ascii') self.__widgets["main_window"].setEnabled( False ) resp = QMessageBox.question( None, self.__widgets["main_window"].windowTitle(), _("Are you sure that you want to send '%s' to the device ?") % cmd, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) self.__widgets["main_window"].setEnabled( True ) if ( resp == QMessageBox.Yes ) : try : self.__ups_handler.RunUPSCommand( self.__current_ups, cmd ) self.gui_status_message( _("Sent '{0}' command to {1}").format( cmd, self.__current_ups ) ) except : self.gui_status_message( _("Failed to send '{0}' ({1})").format( cmd, sys.exc_info()[1] ) ) #------------------------------------------------------------------- # Method called when user clicks on the UPS vars treeview. If the user # performs a double click on a RW var, the GUI shows the update var dialog. def __gui_ups_vars_selected( self, index ) : if True : model = self.__widgets["ups_vars_tree_store"] try : ups_var = model.data( index.siblingAtColumn(1) ).encode('ascii') if ( ups_var in self.__ups_rw_vars ) : # The selected var is RW, then we can show the update dialog cur_val = self.__ups_rw_vars.get(ups_var).decode('ascii') self.__widgets["main_window"].setEnabled( False ) new_val, rc = QInputDialog.getText( None, self.__widgets["main_window"].windowTitle(), _("Enter a new value for the variable.

{0} = {1} (current value)").format( ups_var, cur_val), QLineEdit.Normal, cur_val ) self.__widgets["main_window"].setEnabled( True ) if ( rc ) : try : self.__ups_handler.SetRWVar( ups=self.__current_ups, var=ups_var.decode('ascii'), value=new_val ) self.gui_status_message( _("Updated variable on %s") % self.__current_ups ) # Change the value on the local dict to update the GUI new_val = new_val.encode('ascii') self.__ups_vars[ups_var] = new_val self.__ups_rw_vars[ups_var] = new_val self.__gui_update_ups_vars_view() except : error_msg = _("Error updating variable on '{0}' ({1})").format( self.__current_ups, sys.exc_info()[1] ) self.gui_status_message( error_msg ) else : # User cancelled modification... error_msg = _("No variable modified on %s - User cancelled") % self.__current_ups self.gui_status_message( error_msg ) except : # Failed to get information from the treeview... skip action pass #------------------------------------------------------------------- # Refresh the content of the favorites menu according to the defined favorites def __gui_refresh_favorites_menu( self ) : for current in self.__fav_menu_items : self.__widgets["menu_favorites"].removeAction(current) self.__fav_menu_items = list() items = list(self.__favorites.keys()) items.sort() for current in items : menu_item = QAction( current ) self.__fav_menu_items.append( menu_item ) self.__widgets["menu_favorites"].addAction( menu_item ) menu_item.triggered.connect( lambda: self.__gui_load_favorite( current ) ) if len( items ) > 0 : self.__widgets["menu_favorites_del"].setEnabled( True ) else : self.__widgets["menu_favorites_del"].setEnabled( False ) #------------------------------------------------------------------- # In 'add favorites' dialog, this method compares the content of the # text widget representing the name of the new favorite with existing # ones. If they match, the 'add' button will be set to non sensitive # to avoid creating entries with the same name. def __gui_add_favorite_check_gui_fields( self, fav_name ) : if ( len( fav_name ) > 0 ) and ( fav_name not in list(self.__favorites.keys()) ) : return True else : return False #------------------------------------------------------------------- # Load and parse favorites def __parse_favorites( self ) : if ( not os.path.exists( self.__favorites_file ) ) : # There is no favorites files, do nothing return try : if ( not stat.S_IMODE( os.stat( self.__favorites_path ).st_mode ) == self.DESIRED_FAVORITES_DIRECTORY_MODE ) : # unsafe pre-1.2 directory found os.chmod( self.__favorites_path, self.DESIRED_FAVORITES_DIRECTORY_MODE ) conf = configparser.ConfigParser() conf.read( self.__favorites_file ) for current in conf.sections() : # Check if mandatory fields are present if ( conf.has_option( current, "host" ) and conf.has_option( current, "ups" ) ) : # Valid entry found, add it to the list fav_data = {} fav_data["host"] = conf.get( current, "host" ) fav_data["ups"] = conf.get( current, "ups" ) if ( conf.has_option( current, "port" ) ) : fav_data["port"] = conf.get( current, "port" ) else : fav_data["port"] = "3493" # If auth is defined the section must have login and pass defined if ( conf.has_option( current, "auth" ) ) : if( conf.has_option( current, "login" ) and conf.has_option( current, "password" ) ) : # Add the entry fav_data["auth"] = conf.getboolean( current, "auth" ) fav_data["login"] = conf.get( current, "login" ) try : fav_data["password"] = base64.decodebytes( conf.get( current, "password" ).encode('ascii') ).decode('ascii') except : # If the password is not in base64, let the field empty print(( _("Error parsing favorites, password for '%s' is not in base64\nSkipping password for this entry") % current )) fav_data["password"] = "" else : fav_data["auth"] = False self.__favorites[current] = fav_data self.__gui_refresh_favorites_menu() except : self.gui_status_message( _("Error while parsing favorites file (%s)") % sys.exc_info()[1] ) #------------------------------------------------------------------- # Save favorites to the defined favorites file using ini format def __save_favorites( self ) : # If path does not exists, try to create it if ( not os.path.exists( self.__favorites_file ) ) : try : os.makedirs( self.__favorites_path, mode=self.DESIRED_FAVORITES_DIRECTORY_MODE, exist_ok=True ) except : self.gui_status_message( _("Error while creating configuration folder (%s)") % sys.exc_info()[1] ) save_conf = configparser.ConfigParser() for current in list(self.__favorites.keys()) : save_conf.add_section( current ) for k, v in self.__favorites[ current ].items() : if isinstance( v, bool ) : v = str( v ) save_conf.set( current, k, v ) try : fh = open( self.__favorites_file, "w" ) save_conf.write( fh ) fh.close() self.gui_status_message( _("Saved favorites...") ) except : self.gui_status_message( _("Error while saving favorites (%s)") % sys.exc_info()[1] ) #------------------------------------------------------------------- # Display the about dialog def gui_about_dialog( self, widget=None ) : self.__widgets["main_window"].adjustSize() dialog_ui_file = self.__find_res_file( 'ui', "aboutdialog1.ui" ) dialog = PyQt5.uic.loadUi( dialog_ui_file ) dialog.icon.setPixmap( QPixmap( self.__find_icon_file() ) ) credits_button = QPushButton( dialog ) credits_button.setText( _("C&redits") ) credits_button.setIcon( dialog.style().standardIcon( QStyle.SP_MessageBoxInformation ) ) credits_button.clicked.connect( self.gui_about_credits ) licence_button = QPushButton( dialog ) licence_button.setText( _("&Licence") ) licence_button.clicked.connect( self.gui_about_licence ) dialog.buttonBox.addButton( credits_button, QDialogButtonBox.HelpRole ) dialog.buttonBox.addButton( licence_button, QDialogButtonBox.HelpRole ) self.__widgets["main_window"].setEnabled( False ) dialog.exec() self.__widgets["main_window"].setEnabled( True ) def gui_about_credits( self ) : QMessageBox.about( None, _("Credits"), _(""" Written by: David Goncalves Translated by: David Goncalves - Français Daniele Pezzini - Italiano """).strip() ) def gui_about_licence( self ) : QMessageBox.about( None, _("Licence"), _(""" Copyright (C) 2010 David Goncalves This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """).strip() ) #------------------------------------------------------------------- # Display a message on the status bar. The message is also set as # tooltip to enable users to see long messages. def gui_status_message( self, msg="" ) : text = msg message_id = self.__widgets["status_bar"].showMessage( text.replace("\n", "") ) self.__widgets["status_bar"].setToolTip( text ) #------------------------------------------------------------------- # Display a notification using QSystemTrayIcon with an optional icon def gui_status_notification( self, message="", icon_file="" ) : if ( icon_file != "" ) : icon = QIcon( os.path.abspath( self.__find_res_file( "pixmaps", icon_file ) ) ) else : icon = None self.__widgets["status_icon"].showMessage( "NUT Monitor", message, icon ) #------------------------------------------------------------------- # Connect to the selected UPS using parameters (host,port,login,pass) def connect_to_ups( self, widget=None ) : host = self.__widgets["ups_host_entry"].text() port = int( self.__widgets["ups_port_entry"].value() ) login = None password = None if self.__widgets["ups_authentication_check"].isChecked() : login = self.__widgets["ups_authentication_login"].text() password = self.__widgets["ups_authentication_password"].text() try : self.__ups_handler = PyNUT.PyNUTClient( host=host, port=port, login=login, password=password ) except : self.gui_status_message( _("Error connecting to '{0}' ({1})").format( host, sys.exc_info()[1] ) ) self.gui_status_notification( _("Error connecting to '{0}'\n{1}").format( host, sys.exc_info()[1] ), "warning.png" ) return # Check if selected UPS exists on server... srv_upses = self.__ups_handler.GetUPSList() self.__current_ups = self.__widgets["ups_list_combo"].currentText() if self.__current_ups.encode('ascii') not in srv_upses : self.gui_status_message( _("Device '%s' not found on server") % self.__current_ups ) self.gui_status_notification( _("Device '%s' not found on server") % self.__current_ups, "warning.png" ) return self.__connected = True self.__widgets["ups_connect"].hide() self.__widgets["ups_disconnect"].show() self.__widgets["ups_infos"].show() self.__widgets["ups_params_box"].setEnabled( False ) self.__widgets["menu_favorites_root"].setEnabled( False ) self.__widgets["ups_params_box"].hide() commands = self.__ups_handler.GetUPSCommands( self.__current_ups ) self.__ups_commands = list(commands.keys()) self.__ups_commands.sort() # Refresh UPS commands combo box self.__widgets["ups_commands_combo_store"].clear() for desc in self.__ups_commands : # TODO: Style as "%s
%s" self.__widgets["ups_commands_combo_store"].addItem( "%s\n%s" % ( desc.decode('ascii'), commands[desc].decode('ascii') ) ) self.__widgets["ups_commands_combo"].setCurrentIndex( 0 ) # Update UPS vars manually before the thread self.__ups_vars = self.__ups_handler.GetUPSVars( self.__current_ups ) self.__ups_rw_vars = self.__ups_handler.GetRWVars( self.__current_ups ) self.__gui_update_ups_vars_view() # Try to resize the main window... # FIXME: For some reason, calling this immediately doesn't work right QTimer.singleShot(10, self.__widgets["main_window"].adjustSize) # Start the GUI updater thread self.__gui_thread = gui_updater( self ) self.__gui_thread.start() self.gui_status_message( _("Connected to '{0}' on {1}").format( self.__current_ups, host ) ) #------------------------------------------------------------------- # Refresh UPS vars in the treeview def __gui_update_ups_vars_view( self, widget=None ) : if self.__ups_handler : vars = self.__ups_vars rwvars = self.__ups_rw_vars self.__widgets["ups_vars_tree_store"].removeRows(0, self.__widgets["ups_vars_tree_store"].rowCount()) for k,v in vars.items() : if ( k in rwvars ) : icon_file = self.__find_res_file( "pixmaps", "var-rw.png" ) else : icon_file = self.__find_res_file( "pixmaps", "var-ro.png" ) icon = QIcon( icon_file ) item_icon = QStandardItem(icon, '') item_icon.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren) item_var_name = QStandardItem( k.decode('ascii') ) item_var_name.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren) item_var_val = QStandardItem( v.decode('ascii') ) item_var_val.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren) self.__widgets["ups_vars_tree_store"].appendRow( (item_icon, item_var_name, item_var_val) ) self.__widgets["ups_vars_tree"].resizeColumnToContents( 0 ) self.__widgets["ups_vars_tree"].resizeColumnToContents( 1 ) def gui_init_unconnected( self ) : self.__connected = False self.__widgets["ups_connect"].show() self.__widgets["ups_disconnect"].hide() self.__widgets["ups_infos"].hide() self.__widgets["ups_params_box"].setEnabled( True ) self.__widgets["menu_favorites_root"].setEnabled( True ) self.__widgets["status_icon"].setToolTip( _("Not connected") ) self.__widgets["ups_params_box"].show() # Try to resize the main window... self.__widgets["main_window"].adjustSize() #------------------------------------------------------------------- # Disconnect from the UPS def disconnect_from_ups( self, widget=None ) : self.gui_init_unconnected() # Stop the GUI updater thread self.__gui_thread.stop_thread() del self.__ups_handler self.gui_status_message( _("Disconnected from '%s'") % self.__current_ups ) self.change_status_icon( "on_line", blink=False ) self.__current_ups = None #----------------------------------------------------------------------- # GUI Updater class # This class updates the main gui with data from connected UPS class gui_updater : __parent_class = None __stop_thread = False def __init__( self, parent_class ) : threading.Thread.__init__( self ) self.__parent_class = parent_class def start( self ) : self.__timer = QTimer() self.__timer.timeout.connect(self.__update) self.__timer.start(1000) def __update( self ) : ups = self.__parent_class._interface__current_ups was_online = True # Define a dict containing different UPS status status_mapper = { b"LB" : "%s" % _("Low batteries"), b"RB" : "%s" % _("Replace batteries !"), b"BYPASS" : "Bypass %s" % _("(no battery protection)"), b"CAL" : _("Performing runtime calibration"), b"OFF" : "%s (%s)" % ( _("Offline"), _("not providing power to the load") ), b"OVER" : "%s (%s)" % ( _("Overloaded !"), _("there is too much load for device") ), b"TRIM" : _("Triming (UPS is triming incoming voltage)"), b"BOOST" : _("Boost (UPS is boosting incoming voltage)") } if not self.__stop_thread : try : vars = self.__parent_class._interface__ups_handler.GetUPSVars( ups ) self.__parent_class._interface__ups_vars = vars # Text displayed on the status frame text_left = "" text_right = "" status_text = "" text_left += "%s
" % _("Device status :") if ( vars.get(b"ups.status").find(b"OL") != -1 ) : text_right += "%s" % _("Online") if not was_online : self.__parent_class.change_status_icon( "on_line", blink=False ) was_online = True if ( vars.get(b"ups.status").find(b"OB") != -1 ) : text_right += "%s" % _("On batteries") if was_online : self.__parent_class.change_status_icon( "on_battery", blink=True ) self.__parent_class.gui_status_notification( _("Device is running on batteries"), "on_battery.png" ) was_online = False # Check for additionnal information for k,v in status_mapper.items() : if vars.get(b"ups.status").find(k) != -1 : if ( text_right != "" ) : text_right += " - %s" % v else : text_right += "%s" % v # CHRG and DISCHRG cannot be trated with the previous loop ;) if ( vars.get(b"ups.status").find(b"DISCHRG") != -1 ) : text_right += " - %s" % _("discharging") elif ( vars.get(b"ups.status").find(b"CHRG") != -1 ) : text_right += " - %s" % _("charging") status_text += text_right text_right += "
" if ( b"ups.mfr" in vars ) : text_left += "%s

" % _("Model :") text_right += "%s
%s
" % ( vars.get(b"ups.mfr",b"").decode('ascii'), vars.get(b"ups.model",b"").decode('ascii'), ) if ( b"ups.temperature" in vars ) : text_left += "%s
" % _("Temperature :") text_right += "%s
" % int( float( vars.get( b"ups.temperature", 0 ) ) ) if ( b"battery.voltage" in vars ) : text_left += "%s
" % _("Battery voltage :") text_right += "%sv
" % (vars.get( b"battery.voltage", 0 ).decode('ascii'),) self.__parent_class._interface__widgets["ups_status_left"].setText( text_left[:-4] ) self.__parent_class._interface__widgets["ups_status_right"].setText( text_right[:-4] ) # UPS load and battery charge progress bars self.__parent_class._interface__widgets["progress_battery_charge"].setRange( 0, 100 ) if ( b"battery.charge" in vars ) : charge = vars.get( b"battery.charge", "0" ) self.__parent_class._interface__widgets["progress_battery_charge"].setValue( int( float( charge ) ) ) self.__parent_class._interface__widgets["progress_battery_charge"].resetFormat() status_text += "
%s %s%%" % ( _("Battery charge :"), int( float( charge ) ) ) else : self.__parent_class._interface__widgets["progress_battery_charge"].setValue( 0 ) self.__parent_class._interface__widgets["progress_battery_charge"].setFormat( _("Not available") ) # FIXME: Some themes don't draw text, so swap it with a QLabel? self.__parent_class._interface__widgets["progress_battery_load"].setRange( 0, 100 ) if ( b"ups.load" in vars ) : load = vars.get( b"ups.load", "0" ) self.__parent_class._interface__widgets["progress_battery_load"].setValue( int( float( load ) ) ) self.__parent_class._interface__widgets["progress_battery_load"].resetFormat() status_text += "
%s %s%%" % ( _("UPS load :"), int( float( load ) ) ) else : self.__parent_class._interface__widgets["progress_battery_load"].setValue( 0 ) self.__parent_class._interface__widgets["progress_battery_load"].setFormat( _("Not available") ) # FIXME: Some themes don't draw text, so swap it with a QLabel? if ( b"battery.runtime" in vars ) : autonomy = int( float( vars.get( b"battery.runtime", 0 ) ) ) if ( autonomy >= 3600 ) : info = time.strftime( _("%H hours %M minutes %S seconds"), time.gmtime( autonomy ) ) elif ( autonomy > 300 ) : info = time.strftime( _("%M minutes %S seconds"), time.gmtime( autonomy ) ) else : info = time.strftime( _("%M minutes %S seconds"), time.gmtime( autonomy ) ) else : info = _("Not available") self.__parent_class._interface__widgets["ups_status_time"].setText( info ) # Display UPS status as tooltip for tray icon self.__parent_class._interface__widgets["status_icon"].setToolTip( status_text ) except : self.__parent_class.gui_status_message( _("Error from '{0}' ({1})").format( ups, sys.exc_info()[1] ) ) self.__parent_class.gui_status_notification( _("Error from '{0}'\n{1}").format( ups, sys.exc_info()[1] ), "warning.png" ) def stop_thread( self ) : self.__timer.stop() #----------------------------------------------------------------------- # The main program starts here :-) if __name__ == "__main__" : # Init the localisation APP = "NUT-Monitor" DIR = "locale" gettext.bindtextdomain( APP, DIR ) gettext.textdomain( APP ) _ = gettext.gettext for module in ( gettext, ) : module.bindtextdomain( APP, DIR ) module.textdomain( APP ) gui = interface(sys.argv) gui.exec()