# MicroDowell SpA Community License
# 
# Preamble:
# 
# The intent of this document is to state the conditions under which the 
# Package (ups.pl) may be copied and distributed, such that MicroDowell SpA 
# maintains control over the development and distribution of the Package, 
# while allowing the users of the Package to use the Package in a variety of 
# ways.  
# 
# Authorized electronic distributors of the Package are:
# 
#   * Any MicroDowell SpA site
# 
# Definitions:
# 
# "MicroDowell" refers to MicroDowell SpA., the Copyright Holder of the 
# Package.
# 
# "Package" refers to those files, including, but not limited to, source 
# code, binary executables, images, and scripts, which are distributed by the 
# Copyright Holder.
# 
# "You" is you, if you are thinking about copying or distributing this 
# Package.
# 
# Terms:
# 
# 1. You may use this Package for commercial or non-commercial purposes 
# without charge.
# 
# 2. You may make and give away verbatim copies of this Package for personal 
# use, or for use within your organization, provided that you duplicate all 
# of the original copyright notices and associated disclaimers.  You may not 
# distribute copies of this Package, or copies of packages derived from this 
# Package, to others outside your organization without specific prior written 
# permission from MicroDowell (although you are encouraged to direct them to 
# sources from which they may obtain it for themselves).
# 
# 3. You may apply bug fixes, portability fixes, and other modifications 
# derived from MicroDowell.  A Package modified in such a way shall still be 
# covered by the terms of this license.
# 
# 4. MicroDowell's name and trademarks may not be used to endorse or promote 
# packages derived from this Package without specific prior written 
# permission from ActiveState.
# 
# 5. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# 
# ==============================================================================

#!/usr/local/bin/perl
use strict ;

use IO::File ;       
use IO::Socket ;     
use IO::Select ;
use Socket ;
use Getopt::Long ;                              # for reading command-line options
use Class::Struct ;

sub Init();                 
sub ParseCommandLine();                 
sub HandleData(@);
sub HandleSocket();
sub UpdateConsole();
sub getmessage ($;$);
sub CheckState ($);

my $VERSION      = '0.1';                    	# program version
my $PROGRNAME    = 'upscheck (Perl)' ;   	      # program name

my $SHOW_HELP    = 0  ;  								# if TRUE prints help info
my $IPADDR       = "127.0.0.1"  ;  					# TCP server IP address
my $SLAVE        = 0  ;  								# MASTER/SLAVE connection

my $con ;
my $sel ;
my $tcpPort = 18881 ;
my $Data ;     									      # record data from UpsEye SERVER
my @Token ;    									      # contains splitted record data
my $ups ;

my %MESSAGES = 
	(
	  -1 	=> "Resolver internal error",
     0 	=> "UPS on Line",
     1 	=> "UPS on Battery",
     2 	=> ", Battery Low",
     3 	=> ", Battery End",
     4 	=> "UPS on Standby",
     5 	=> "UPS output Overload",
     6 	=> "UPS generic FAULT",
     7 	=> "Maximum internal temperature reached: "
     ) ;

#-----------------------------------------------------------------------------------
use constant eveONLINE => 0 ;
use constant eveONBATT => 1 ;
use constant eveBLOW => 2 ;
use constant eveBEND => 3 ;
use constant eveSTDBY => 4 ;
use constant eveOVERLOAD => 5 ;
use constant eveFAULT => 6 ;
use constant eveHTEMP => 7 ;

struct _MEASURES =>
	{
   CommStatus => '$',                           # Communication Status: <>0 = Error
	Counter => '$',                              # Contatore Polling
	RunTime => '$',						         	# battery runtime
	State => '$',								         # state (long)
	OldState => '$',						            # Old state (long)
	ShortStatus => '$',                          # short status
	OutConf => '$',                              # output status
	WeekDay => '$',                              # Week Day (from UPS)
	Hour => '$',                                 # Hours (from UPS)
	Min => '$',                                  # Minutes (from UPS)
	Sec => '$',                                  # Seconds (from UPS)
	Vin 	=> '$',								         # input voltage (VAC)
	Vout 	=> '$',								         # output voltage (VAC)
	Vbat 	=> '$',								         # battery voltage (VCC)
   Iout => '$',                                 # Output current (A)
   Load 	=> '$',								         # UPS Load (VA)
   LoadPerc => '$',                             # UPS Load in percent (%)
	InFreq 	=> '$',							         # input frequency
	OutFreq 	=> '$',							         # output frequency

	Temperature 	=> '$',					         # temperature (°C)
   BattMin => '$',                              # Minimum battery level (VCC)
   BattPerc => '$',                             # Battery charge in percent (%)
   BattLowPerc => '$',                          # Battery LOW in percent (%)
   RtimeEmpty => '$',                           # Runtime to Empty (sec)
   PercEmpty => '$',                            # Percent to Empty (%) 0=discharged, 100=Charged
   BattRuntime => '$',                          # UPS time in battery mode (sec)
   UpsRuntime => '$',                           # UPS time in standard mode (min)
   OutStatus => '$',                            # Status of the output ports
   Icharger => '$',                             # Charger current (A)

	RawData => '$',							         # 
	} ;

struct _UPSMODEL =>
	{
	EnterpriseModel => '$',                      # UPS >= 2000VA
	GE2KVA => '$',                               # UPS >= 2000VA
   Model => '$',                                # UPS model
   SerialNumber => '$',                         # UPS Serial number
   NetID => '$',                                # network S/N
   NetName => '$',                              # Network name
	Type => '$',								         # UPS type
	FwVersion 	=> '$',						         # FW release
	FwsubVersion => '$',                         # Firmware SUBversion
	FWMajor 	=> '$',							         # Major version
	FWMinor 	=> '$',							         # Minor version
	BattNumber => '$',   					         # battery number
	YearProd => '$',     					         # year of production
	MonthProd => '$',    					         # month of production
	DayProd => '$',      					         # day of production
	HWver => '$',        					         # Hardware version
	BRDver => '$',       					         # Board version
	TcpFWMajor 	=> '$',						         # Tcp module Major version
	TcpFWMinor 	=> '$',						         # Tcp module Minor version
	} ;


struct _UPS =>
	{
	Name			=> '$',						         # UPS name
	Type			=> '$',						         # UPS type
	Master		=> '$',						         # Master mode program
	BatteryType	=> '$',						         # battery type
	TScale		=> '$',						         # temperature scale 
	Com 			=> '$',						         # com port structure reference
	ComState 	=> '$',						         # 0: startup, 1: polling data, 2: no communication
	SckState 	=> '$',						         # 0: startup, 1: receiving data, 2: connection lost
	SerialPort	=> '$',
	SerialSpeed	=> '$',
	InBuffer 	=> '$',
	OutBuffer 	=> '$',
	Command 		=> '@',
	LangID		=> '$',						         # UPS language interface
	PollState 	=> '$',
	PollCmd 		=> '$',
	PollInterval	=> '$',					         # polling interval
	RxTimeout 	=> '$',
	TxTimeout 	=> '$',
	ErrCount		=> '$',						         # number of polling without answer 	
 	MsgFile     => '$',         			         # file for message delivery
 	MsgCmd      => '$',         			         # command to delivery message
	Model			=> '_UPSMODEL',			         # UPS model struct reference
	Measures 	=> '_MEASURES',			         # UPS measures struct reference
} ;


#-----------------------------------------------------------------------------------
#                               Program start
#-----------------------------------------------------------------------------------
   Init() ;
   ParseCommandLine() ;
   print  " ... trying connection to $IPADDR \n";

   if ($SLAVE eq 1)
      { $tcpPort = 18882 ; }

   $con = IO::Socket::INET->new(PeerAddr => $IPADDR,
                                PeerPort => $tcpPort,
                                Proto    => "tcp",
                                Type     => SOCK_STREAM,
                                Timeout  => 10) ;
   die "Could't connect to $IPADDR: $!\n" unless $con ;

   $sel = IO::Select->new() ;
   $sel->add($con) ;

   while (1)
   	{
      HandleSocket();
   	}

   $con->close() ;

sub HandleSocket()
{
   my @ready = $sel->can_read(.1) ;
	foreach my $fh( @ready )
		{
		if ((defined($_ = $fh->getline)))
			{
		      # i remove the trailing and leading "\r\n"
	   	   s/\r// ;
   	   	s/\n// ;
      		s/-{1}/ / ;
      		# i remove the first occurrence of "-" to recognize command
            
      		# @Token contains the different tokens of the message (separated by ' ')
      		$Data = $_ ;
      		@Token = split(/ /, $_) ;                    # split of the tokens

      		# This particular construct emulate a "switch-case" like in C
      		$_ = $Token[0] ;  							      # @Token[0] is the letter which identifies the command
      		# now i analyze the contents of '$_'

      		# is UpsEye SERVER sending some DATA?
      		/D/i && do     								      # looking for 'D' message (DATA)
         		{ 
         		if ($Token[1] eq 2000)
         			{
         			}
         		elsif ($Token[1] eq 2001)
         			{
                  my @tokenbuf = ($Data =~ /\"(.{8})\"/gi) ;
                  $ups->{Model}->{Model} = $tokenbuf[0] ;
                  $ups->{Model}->{SerialNumber} = $tokenbuf[1] ;
                  $ups->{Model}->{NetID} = $tokenbuf[2] ;
                  $ups->{Model}->{NetName} = $tokenbuf[3] ;
         			HandleData(@Token) ; 
         			}
         		else
         			{	
         			HandleData(@Token) ; 
         			}
         		} ;			
			}
		}
}

sub UpdateConsole()
{
   my $outstring;
   
   if ($^O eq "MSWin32")
      {
      system ("cls") ;
      }
   else
      {
      system ("clear") ;
      }
   printf "#------------------------------------------------------------------------#\n";
   printf "#\tConnected to $IPADDR, Press Ctrl-C to exit program\n" ;
   printf "#------------------------------------------------------------------------#\n";
   printf "UPS model:    %s, %s\n\n", $ups->{Model}->{Model}, $ups->{Model}->{SerialNumber} ; 
   printf "UPS Status:   %s \n\n", CheckState(hex($ups->{Measures}->{State})) ; 
   printf "Measures    \n\n";  
   printf("%d Vin:%.1f Vout:%.1f Vbat:%.1f Temp:%.1f Load:%4dVA LoadP:%3d%% \r",
                     $ups->{Measures}->{Counter},
                     $ups->{Measures}->{Vin}, $ups->{Measures}->{Vout}, $ups->{Measures}->{Vbat}, $ups->{Measures}->{Temperature},
                     $ups->{Measures}->{Load}, $ups->{Measures}->{LoadPerc}) ;
}

#-----------------------------------------------------------------------------------
# ParseCommandLine() - parse the command line options
#
# This function reads the configuration file for WebAssay. There are
# three possible sources, which are tried in the following order:
#	1. -f switch on the command-line
#	2. $HOME/.webassay
#	3. local site configuration file
#-----------------------------------------------------------------------------------
sub ParseCommandLine()
{
    my @switches = (
                    'help',         \$SHOW_HELP,
                    'ip=s',         \$IPADDR,
                    's',            \$SLAVE,
                   );

    &GetOptions(@switches) || die "use -help switch to display brief help\n";


    if ($SHOW_HELP)
      {
      print <<EofHelp;
$PROGRNAME, v$VERSION

Usage: UpsEye.pl [options]

   -help             : shows this help message
   -ip=ip_addr       : sets the UPS ip address
   -s                : slave/master flag
EofHelp
      exit 0;
      }
   
}

#-----------------------------------------------------------------------------
# HandleData: checks and manages the received DATA
#-----------------------------------------------------------------------------
sub HandleData (@) 
{
   my $NumParms = @_ - 2 ;
   my $CmdID = $_[1] ;
   my @Parms = @_[2..@_-1] ;
   my $Cmp ;
   my $i ;   
   
   $_ = $CmdID ;  # Command ID
   # analyzing the contents of '$_'

   /2000/ && do     # D-2000 00 "MicroDowell SpA - TCP/IP - v0.0 zeta" v7.25 Socket[9]
      {  
   	$ups->{Model}->{TcpFwMajor} = $Parms[6] ;
   	$ups->{Model}->{TcpFwMinor} = $Parms[6] ;      
      } ;
   /2001/ && do     # D-2001 "ENT30   " "12345678" "FFFFFFFF" "FFFFFFFF" 2002/04/04 0.0 1.2 0.0.1 4
      {  
   	$ups->{Model}->{YearProd} = (split(/\//,$Parms[$#Parms-4]))[0] ;
   	$ups->{Model}->{MonthProd} = (split(/\//,$Parms[$#Parms-4]))[1] ;
   	$ups->{Model}->{DayProd} = (split(/\//,$Parms[$#Parms-4]))[2] ;
   	$ups->{Model}->{HWver} = $Parms[$#Parms-3] ;
   	$ups->{Model}->{BRDver} = $Parms[$#Parms-2] ;
   	$ups->{Model}->{FwMajor} = (split(/\//,$Parms[$#Parms-1]))[0] ;
   	$ups->{Model}->{FwMinor} = (split(/\//,$Parms[$#Parms-1]))[1] ;
   	$ups->{Model}->{FwsubVersion} = (split(/\//,$Parms[$#Parms-1]))[2] ;
   	$ups->{Model}->{BattNumber} = $Parms[$#Parms] ;
      } ;
   /2002/ && do     # D-2002 IP=x.x.x.x MASK=y.y.y.y GATEWAY=z.z.z.z
      {  
      } ;
   /2100/ && do     # D-2100 14313 00 00000718 0718 33 000 00 00 1 00 49 51
      {
      $ups->{Measures}->{Counter} = $Parms[0] ; 
		if (int($Parms[1]) ne 0)
			{
			# there are communication problems with the UPS
			if ($ups->{ComState} eq 0)
				{
            # ComLost
				}
			}
		else
			{
			if ($ups->{ComState} ne 0)
				{
            # ComStart
				}
	      $ups->{Measures}->{OldState} = $ups->{Measures}->{State} ;   		      # save old state
	      $ups->{Measures}->{State} = $Parms[2] ;
		   if ($ups->{Measures}->{OldState} ne $ups->{Measures}->{State})
		      {
		   	UpdateConsole();
		      }
			$ups->{Measures}->{ShortStatus} = $Parms[3] ;
	      $ups->{Measures}->{OutConf} = $Parms[4] ;                              # OutConfig
	      $ups->{Measures}->{WeekDay} = $Parms[8] ;                              # UPS day of week
	      $ups->{Measures}->{Hour} = $Parms[9] ;                                 # UPS Hour
         $ups->{Measures}->{Min} = $Parms[10] ;                                 # UPS Min
	      $ups->{Measures}->{Sec} = $Parms[11] ;                                 # UPS Sec
	      }
      $ups->{ComState} = int($Parms[1]) ; 
      } ;
   /2101/ && do     # D-2101 227.3 229.2 55.2 32.3 0.0 0.0 0.0
      {  
      $ups->{Measures}->{Vin} =  $Parms[0] ;       							# Vinput
      $ups->{Measures}->{Vout} = $Parms[1] ;   								# Voutput
      $ups->{Measures}->{Vbat} = $Parms[2] ;    								# Vbattery (V)
      $ups->{Measures}->{Temperature} = $Parms[3] ;  						# Temperature
      $ups->{Measures}->{Iout} = $Parms[4] ;  									# Ioutput
      $ups->{Measures}->{InFreq} = $Parms[5] ;  								# InFreq
      $ups->{Measures}->{OutFreq} = $Parms[6] ;  								# OutFreq
      } ;
   /2102/ && do     # D-2102 0 0 100 -1 0 5724 452 0
      { 
      # check if i'm in battery mode + communications OK...
      $ups->{Measures}->{LoadPerc} = $Parms[0] ;                       # Load %
      $ups->{Measures}->{Load} = $Parms[1] ;         						# Load (VA)
      $ups->{Measures}->{BattPerc} = $Parms[2] ;                       # Battery charge in %
      $ups->{Measures}->{RtimeEmpty} = $Parms[3] ;   						# Runtime to empty (sec)
      $ups->{Measures}->{PercEmpty} = $Parms[4] ;                      # Perc to empty 
		$ups->{Measures}->{UpsRuntime} = $Parms[5] ;							# UPS time in standard mode (min)
		$ups->{Measures}->{BattRuntime} = $Parms[6] ;							# UPS time in battery mode (sec)
		$ups->{Measures}->{BattLowPerc} = $Parms[7] ;							# Battery LOW in percent (%)
      if ($ups->{ComState} eq 0)
         {
         printf("%d Vin:%.1f Vout:%.1f Vbat:%.1f Temp:%.1f Load:%4dVA LoadP:%3d%% \r",
                     $ups->{Measures}->{Counter},
                     $ups->{Measures}->{Vin}, $ups->{Measures}->{Vout}, $ups->{Measures}->{Vbat}, $ups->{Measures}->{Temperature},
                     $ups->{Measures}->{Load}, $ups->{Measures}->{LoadPerc}) ;
         }
      } ;
}

sub Init () 
{
	my @result ;
	my $i;
	my $section ;
	
	$| = 1 ;
	$ups = _UPS->new ;

	$ups->{PollState} = 0 ; 
	$ups->{SckState} = 0 ;
	$ups->{ComState} = 0 ; 
	$ups->{ErrCount} = 0 ;
	$ups->{Measures}->{Counter} = 0 ;
	$ups->{Measures}->{State} = 0x80000000 ;
	$ups->{Measures}->{OutConf} = 0 ;
	$ups->{Measures}->{Day} = 0 ;
	$ups->{Measures}->{Hour} = 0 ;
	$ups->{Measures}->{Min} = 0 ;
	$ups->{Measures}->{Sec} = 0 ;

	# Measures
   $ups->{Measures}->{ShortStatus} = 0 ;
	$ups->{Measures}->{LoadPerc} = 0 ;
	$ups->{Measures}->{Vin} = 0 ;
	$ups->{Measures}->{Vbat} = 0 ;
	$ups->{Measures}->{Vout} = 0 ;
   $ups->{Measures}->{Iout} = 0 ;
   $ups->{Measures}->{Temperature} = 0 ;
   $ups->{Measures}->{Icharger} = 0 ;
	# Config
   $ups->{Measures}->{BattMin} = 0 ;
   $ups->{Measures}->{UpsRuntime} = 0 ;
   $ups->{Measures}->{BattRuntime} = 0 ;
   $ups->{Measures}->{BattLowPerc} = 0 ;
	$ups->{Measures}->{OutStatus} = 0 ;				
	# Battery status
   $ups->{Measures}->{BattPerc} = 0 ;
   $ups->{Measures}->{RtimeEmpty} = 0 ;
   $ups->{Measures}->{RtimeEmpty} = 0 ;
   $ups->{Measures}->{PercEmpty} = 0 ;
   $ups->{Measures}->{Load} = 0 ;
   $ups->{Measures}->{OutFreq} = 0 ;
   $ups->{Measures}->{InFreq} = 0 ;
  	$ups->{Measures}->{State} = 0x0 ;
}

#-----------------------------------------------------------------------------
# getmessage: returns a message string related to a particular event
#-----------------------------------------------------------------------------
sub getmessage ($;$)
{
  	my ($msgid,$optional) = @_;
	my $message ;
	
	if ($optional)
		{
		$message = $MESSAGES{$msgid} ;
		$message =~ s/%s/$optional/g ;
		return $message ;
		}
	else
		{
  		return $MESSAGES{$msgid};
		}

}

sub CheckState ($)
{
	my $state = shift ;

	#-----------------------------------------------------------------------------
	# ... check the state if the value is changed, ...
	#-----------------------------------------------------------------------------
	
	if ($state & 0x80)   # UPS in Standby mode
		{
		# ... UPS is in standby
		return getmessage(eveSTDBY) ;
		}
	elsif ($state & 0x01) # UPS in battery mode
		{
		if ($state & 0x04) # UPS in Battery END
			{
			# ... ups batteries discharged
		   return (getmessage(eveONBATT).getmessage(eveBEND)) ;
			}
		elsif ($state & 0x02) # UPS in Battery LOW
			{
		   return (getmessage(eveONBATT).getmessage(eveBEND)) ;
			}
		else
			{
		   return getmessage(eveONBATT) ;
			}
		}
	else 
		{
		return getmessage(eveONLINE) ;
		}
}
