# 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

package MyGlobalsCl ;

use strict ;
use POSIX;
use English ;        # for variable extension
use IO::File ;       # 
use IO::Socket ;     # to connect to UpsEye SERVER
use IO::Select;
use Socket;
use Fcntl;
use Tie::RefHash;
use Sys::Hostname;
use Getopt::Long ;   # for reading command-line options

use Class::Struct ;
use Exporter ;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION $ups $connection $OS_OSX $OS_win %iniConfig 
				$INIFILE $PROGPATH $INTERACTIVE $UpsSchedule) ;

use constant IDENT_1 => "D-2000 %02X \"MicroDowell SpA - TCP/IP - v0.0 gamma\" v0.0 Socket[%d]\r\n" ;
use constant IDENT_2 => "D-2002 IP=%s MASK=0.0.0.0 GATEWAY=0.0.0.0 SLAVE\r\n" ;

use constant UPS_SCKSTATE_INIT_MODE => 0 ;   # startup
use constant UPS_SCKSTATE_LIST_MODE => 1 ;   # polling data
use constant UPS_SCKSTATE_COMM_LOST => 2 ;   # no communication

use constant ERR_COM_NO_CHARS      => -999 ; # nessun carattere dalla porta seriale
use constant ERR_MSG_TOO_SHORT     => -998 ; # messaggio troppo breve: non arriva sino al Checksum
use constant ERR_MSG_CHECKSUM      => -997 ; # checksum non valido
use constant ERR_COM_TIMEOUT       => -996 ; # timeout in lettura
use constant COMMAND_NOT_VALID     => -50  ; # comando non valido
use constant PARAMETER_NOT_VALID   => -51  ; # parametro non valido
use constant ERR_UPS_NOT_FOUND     => -10  ; # non trovo un UPS
use constant ERR_COM_PORT_OPEN     => -11  ; # impossibile aprire la porta di comunicazione
use constant ERR_COM_SET_PORT      => -12  ; # impossibile configurare la porta di comunicazione
use constant ERR_COM_UNDEF_PORT    => -20  ; # porta non valida o non esistente
use constant ERR_USB_DRIVER        => -13  ; # impossibile aprire il driver USB
use constant ERR_USB_COMM_KO       => -14  ; # interfaccia USB OK: gruppo spento o guasto
use constant ERR_UPS_UNKNOWN       => -30  ; # impossibile identificare l'UPS
use constant ERR_PRG_THREAD        => -40  ; # impossibile creare uno thread
use constant ERR_PRG_INVALID_DATA  => -60  ; # dati non validi

use constant CMD_ACK              => 0x06 ; # ACK
use constant CMD_NACK             => 0x15 ; # NACK

#-----------------------------------------------------------------------------------
# B.Box interactive old commands
#-----------------------------------------------------------------------------------
use constant CMD_GET_STATUS_OLD   => 0x01 ; # comando di acquisizione STATUS
use constant CMD_GET_MODEL_OLD    => 0x0A ; # comando di acquisizione MODELLO
#-----------------------------------------------------------------------------------

use constant CMD_GET_STATUS       => 0x00 ; # comando di acquisizione STATUS
use constant CMD_GET_MEASURES     => 0x01 ; # comando di acquisizione MISURE
use constant CMD_GET_CONFIG       => 0x02 ; # comando di acquisizione CONFIGURAZIONE
use constant CMD_GET_BATT_STAT    => 0x03 ; # comando di acquisizione Battery+Load Status
use constant CMD_GET_MASK         => 0x07 ; # comando di acquisizione MASCHERA PUNTI
use constant CMD_SET_TIMER        => 0x20 ; # comando di CONFIGURAZIONE TIMER
use constant CMD_BATT_TEST        => 0x21 ; # comando di CONFIGURAZIONE TEST BATTERIA
use constant CMD_GET_BATT_TEST    => 0x22 ; # comando di       LETTURA  TEST BATTERIA
use constant CMD_BUZZER_OFF       => 0x23 ; # comando di SHUTDOWN BUZZER
use constant CMD_SD_ONESHOT       => 0x40 ; # comando di SCRITTURA SHUTDOWN ONESHOT
use constant CMD_GET_SD_ONESHOT   => 0x41 ; # comando di  LETTURA  SHUTDOWN ONESHOT
use constant CMD_SET_SCHEDULE     => 0x42 ; # comando di SCRITTURA SCHEDULE
use constant CMD_GET_SCHEDULE     => 0x43 ; # comando di  LETTURA  SCHEDULE
use constant CMD_GET_EEP_BLOCK    => 0x50 ; # comando di  LETTURA  BLOCCO EEPROM
use constant CMD_INIT             => 0xF0 ; # comando di REINIZIALIZZAZIONE UPS

use constant LEN_ACK              => 1 ; # ACK
use constant LEN_NACK             => 2 ; # NACK

use constant LEN_GET_STATUS       => 1 ; # comando di acquisizione STATUS
use constant LEN_GET_MEASURES     => 1 ; # comando di acquisizione MISURE
use constant LEN_GET_CONFIG       => 1 ; # comando di acquisizione CONFIGURAZIONE
use constant LEN_GET_BATT_STAT    => 1 ; # comando di acquisizione Battery+Load Status
use constant LEN_GET_MASK         => 1 ; # comando di acquisizione MASCHERA PUNTI
use constant LEN_SET_TIMER        => 5 ; # comando di CONFIGURAZIONE TIMER
use constant LEN_BATT_TEST        => 4 ; # comando di CONFIGURAZIONE TEST BATTERIA
use constant LEN_GET_BATT_TEST    => 1 ; # comando di       LETTURA  TEST BATTERIA
use constant LEN_BUZZER_OFF       => 1 ; # comando di SHUTDOWN BUZZER
use constant LEN_SD_ONESHOT       => 8 ; # comando di SCRITTURA SHUTDOWN ONESHOT
use constant LEN_GET_SD_ONESHOT   => 1 ; # comando di  LETTURA  SHUTDOWN ONESHOT
use constant LEN_SET_SCHEDULE     => 8 ; # comando di SCRITTURA SCHEDULE
use constant LEN_GET_SCHEDULE     => 2 ; # comando di  LETTURA  SCHEDULE
use constant LEN_GET_EEP_BLOCK    => 3 ; # comando di SCRITTURA BLOCCO EEPROM
use constant LEN_INIT             => 1 ; # comando di REINIZIALIZZAZIONE UPS

use constant ERR_I2C_BUSY       	=> 0x01	; # I²C bus busy (e²prom)
use constant ERR_CMD_CHECKSUM    => 0x10	; # Checksum not valid
use constant ERR_CMD_UNRECOG     => 0x11	; # unrecognized command
use constant ERR_EEP_NOBLOCK    	=> 0x08	; # WRITE: eeprom address not multiple of 8
use constant ERR_EEP_OOBOUND    	=> 0x09	; # READ: eeprom address out of bound (with size)
use constant ERR_EEP_WADDR1     	=> 0x28	; # error writing e²prom address
use constant ERR_EEP_WSADDR1    	=> 0x29	; # error writing e²prom subaddress
use constant ERR_EEP_RDATA      	=> 0x2A	; # error reading e²prom data
use constant ERR_EEP_WADDR2     	=> 0x50	; # error writing e²prom address
use constant ERR_EEP_WSADDR2    	=> 0x51	; # error reading e²prom subaddress
use constant ERR_EEP_WDATA      	=> 0x52	; # error writing e²prom data
use constant ERR_EEP_WADDRVER   	=> 0x53	; # error writing e²prom address during data verification
use constant ERR_EEP_WDATAVER   	=> 0x54	; # error verification e²prom data
use constant ERR_EEP_VERIFY     	=> 0x55	; # e²prom data are different from those in the write buffer
use constant ERR_EEP_CHECKSUM   	=> 0x90	; # e²prom checksum error

#=======================================================
# Indirizzi delle variabili memorizzate nella EEPROM: #
#=======================================================
use constant  EEP_MAGIC        => 0x00 ; # magic number: deve essere a 0xAA
use constant  EEP_CHECKSUM     => 0x01 ; # XOR longitudinale da 0x?? a 0x??
use constant  EEP_OUT_CONFIG   => 0x02 ; # vedi documentazione
use constant  EEP_STATUS       => 0x04 ; # Status UPS

use constant  EEP_THRESHOLD_0  => 0x08 ; # Blocco 0 con gli threshold
use constant  EEP_MEAN_MIN     => 0x08 ; # UPS minimum AC voltage intervention point 
use constant  EEP_MEAN_MAX     => 0x0A ; # UPS maximum AC voltage intervention point 
use constant  EEP_AVR_MIN      => 0x0C ; # AVR minimum AC voltage intervention point
use constant  EEP_AVR_MAX      => 0x0E ; # AVR maximum AC voltage intervention point

use constant  EEP_THRESHOLD_1  => 0x10 ; # Blocco 1 con gli threshold
use constant  EEP_BLOW_VALUE   => 0x10 ; # Battery LOW threshold voltage
use constant  EEP_BEND_VALUE   => 0x12 ; # Battery END threshold voltage
use constant  EEP_VMETER_0     => 0x14 ; # Led 0 Voltage indicator threshold
use constant  EEP_VMETER_1     => 0x16 ; # Led 1 Voltage indicator threshold

use constant  EEP_THRESHOLD_2  => 0x18 ; # Blocco 2 con gli threshold
use constant  EEP_VMETER_2     => 0x18 ; # Led 2 Voltage indicator threshold
use constant  EEP_VMETER_3     => 0x1A ; # Led 3 Voltage indicator threshold
use constant  EEP_VMETER_4     => 0x1C ; # Led 4 Voltage indicator threshold

use constant  EEP_FAULT_POINTS => 0x20 ; # number of fault points (in an half sine wave) after which USP enter battery mode
use constant  EEP_CHARGER_MIN  => 0x21 ; # Threshold voltage for battery charger

use constant  EEP_TEMP_MAX     => 0x28 ; # Maximum temperature: over this value an alarm will be generated  
use constant  EEP_TEMP_FAULT   => 0x2A ; # Fault temperature: over this value, UPS will shut down immediately 
use constant  EEP_FANSPEED_0   => 0x2C ; # Temperature for fan activation (low speed)
use constant  EEP_FANSPEED_1   => 0x2E ; # Temperature for fan activation (medium speed)

use constant  EEP_FANSPEED_2   => 0x30 ; # Temperature for fan activation (high speed)

use constant  EEP_IOUT         => 0x38 ; # 
use constant  EEP_IOUT_OVRLD   => 0x38 ; # Maximum current: over this value an alarm will be generated 
use constant  EEP_IOUT_FAULT   => 0x3A ; # Fault current: over this value, UPS will shut down immediately
use constant  EEP_PWRMTR_0     => 0x3C ; # Led 0 Power indicator threshold 
use constant  EEP_PWRMTR_1     => 0x3E ; # Led 1 Power indicator threshold 

use constant  EEP_PWRMTR_2     => 0x40 ; # Led 2 Power indicator threshold 
use constant  EEP_PWRMTR_3     => 0x42 ; # Led 3 Power indicator threshold 
use constant  EEP_PWRMTR_4     => 0x44 ; # Led 4 Power indicator threshold 
use constant  EEP_PWRMTR_5     => 0x46 ; # Between "Full power" and "Overload" Power indicator level 

use constant  EEP_INPUT_MASK_0 => 0x48 ; # Half sine input mask (0÷7 of 10 points)
use constant  EEP_INPUT_MASK_8 => 0x50 ; # Half sine input mask (8÷9 of 10 points)

use constant  EEP_MIN_VBATT    => 0x60 ; # Minimum battery voltaged reached from the last power-on
use constant  EEP_RUNTIME_H    => 0x62 ; # Working time in minutes (max ˜ 32 years) (MSB first)
use constant  EEP_BAT_TIME_M   => 0x65 ; # Time in Battery mode: seconds*2 (max ˜ 390 days). (MSB first)


use constant  EEP_UPS_MODEL    => 0x80 ; # Model of the UPS in ASCII. For the Enterprise line, the structure is:
                                    ; #    ENTxxyyy   
                                    ; # where:	 xx = Power rating in watts/100 (zero padded at left)	
                                    ; #        yyy = model variants (if not defined, space padded to the right)

use constant  EEP_NETWRK_ID    => 0x88 ; # Network ID in ASCII (not used) - space padded
use constant  EEP_NETWRK_NAME  => 0x90 ; # Network Name in ASCII (not used) - space padded
use constant  EEP_SERIAL_NUM   => 0x98 ; # Serial Number in ASCII - zero padded (at left)

use constant  EEP_PROD_DATE    => 0xA0 ; # 
use constant  EEP_PROD_YEAR    => 0xA0 ; # Year of production (add 2000)
use constant  EEP_PROD_WEEK    => 0xA1 ; # Week of production [0÷51]
use constant  EEP_PROD_HW_VER  => 0xA2 ; # Hardware version: 2 nibbles ?  Major and Minor number.
use constant  EEP_PROD_BRD_VER => 0xA3 ; # Board version: 2 nibbles ?  Major and Minor number.
use constant  EEP_PROD_FW_VER  => 0xA4 ; # Firmware version: 2 nibbles ?  Major and Minor number.
use constant  EEP_PROD_FW_SVER => 0xA5 ; # Firmware SUBversion: 2 nibbles ?  Major and Minor number.
use constant  EEP_PROD_BTTRS   => 0xA6 ; # number of batteries installed in the UPS


use constant  EEP_BATT_SUBST   => 0xA8 ; # battery replacement block
use constant  EEP_BATT_YEAR_0  => 0xA8 ; # Year of 1st battery replacement (add 2000)
use constant  EEP_BATT_WEEK_0  => 0xA9 ; # Week of 1st battery replacement
use constant  EEP_BATT_YEAR_1  => 0xAA ; # Year of 2nd battery replacement (add 2000)
use constant  EEP_BATT_WEEK_1  => 0xAB ; # Week of 2nd battery replacement
use constant  EEP_BATT_YEAR_2  => 0xAC ; # Year of 3rd battery replacement (add 2000)
use constant  EEP_BATT_WEEK_2  => 0xAD ; # Week of 3rd battery replacement
use constant  EEP_BATT_YEAR_3  => 0xAE ; # Year of 4th battery replacement (add 2000)
use constant  EEP_BATT_WEEK_3  => 0xAF ; # Week of 4th battery replacement


use constant  EEP_SCHEDULE     => 0xC8 ; # Start of SCHEDULING table
use constant  EEP_SCHEDULE_0   => 0xC8 ; # 1st Scheduling slot
use constant  EEP_SCHEDULE_1   => 0xD0 ; # 2nd Scheduling slot
use constant  EEP_SCHEDULE_2   => 0xD8 ; # 3rd Scheduling slot
use constant  EEP_SCHEDULE_3   => 0xE0 ; # 4th Scheduling slot
use constant  EEP_SCHEDULE_4   => 0xE8 ; # 5th Scheduling slot
use constant  EEP_SCHEDULE_5   => 0xF0 ; # 6th Scheduling slot

#use constant DETACHED_PROCESS => 0 ;   
#-----------------------------------------------------------------------------------
# Constants definition
#-----------------------------------------------------------------------------------
use constant CMD_POLL_DATA => 48 ;
use constant CMD_TIMER => 49 ;
use constant CMD_SCHEDULE => 50 ;
use constant CMD_STDBY => 51 ;
use constant CMD_BEND => 52 ;
use constant CMD_BTEST => 53 ;
use constant CMD_BUZOFF => 54 ;
use constant CMD_POLL_MODEL => 58 ;
#-----------------------------------------------------------------------------------
use constant MAX_CLIENTS => 20 ;

#-----------------------------------------------------------------------------------
# Events constants
#-----------------------------------------------------------------------------------
use constant eveSTART => 0 ;
use constant eveSTOP => 1 ;
use constant eveNOC => 2 ;
use constant eveCOMSTART => 3 ;
use constant eveCOMLOST => 4 ;
use constant eveBMODE => 5 ;
use constant eveBLOW => 6 ;
use constant evePOWON => 7 ;
use constant eveOVERLOAD => 8 ;
use constant eveHTEMP => 9 ;
use constant eveGENFAULT => 11 ;
use constant eve10TOSD => 12 ;
use constant eve1TOSD => 13 ;
use constant eveSYSSD => 14 ;
use constant eveSCHESD => 15 ;
#-----------------------------------------------------------------------------------
use constant eveLINEON => 30 ;
use constant eveSTDBY_CHAR => 31 ;
use constant eveSTDBY_DISCH => 32 ;
use constant eveBEND => 33 ;
use constant eveMSG_BMODE => 34 ;
use constant eveMSG_BLOW => 35 ;
use constant eveBMTEST => 36 ;
use constant eveOLTESTOK => 37 ;
use constant eveOLTESTKO => 38 ;
use constant eveCANCELSCHE => 39 ;
use constant eveLOADOK => 40 ;
use constant eveTEMPOK => 41 ;
#-----------------------------------------------------------------------------------
use constant eveERR_INI => 60 ;
use constant eveERR_MODEL => 61 ;
use constant eveERR_GEN => 62 ;
use constant eveNET_CLOSE => 63 ;
use constant eveNET_NEW => 64 ;
use constant eveERR_TCPCFG => 65 ;
#-----------------------------------------------------------------------------------
use constant eveSDATA => 80 ;
use constant eveSCHEON => 81 ;
use constant evePCOFF => 82 ;

#-----------------------------------------------------------------------------------
# Warning messages notified to all clients
#-----------------------------------------------------------------------------------
my %MESSAGES = 
	(
	  -1 	=> "Resolver internal error",
     0 	=> "* * * UPS Monitoring Started * * *",
     1 	=> "* * * UPS Monitoring Stopped * * *",
     2 	=> "Unable to communicate with UPS ",
     3 	=> "UPS communication established ",
     4 	=> "UPS communication interrupted ",
     5 	=> "UPS on Battery",
     6 	=> "UPS on Battery Low",
     7 	=> "Mains power restored: UPS on Line",
     8 	=> "UPS output Overload: ",
     9 	=> "Maximum internal temperature reached: ",
     10  => "UPS self test failed",
     11 	=> "UPS generic FAULT",
     12 	=> "WARNING !! The SYSTEM will shutdown within ten minutes",
     13 	=> "WARNING !! The SYSTEM will shutdown within one minute. Save Files, quit Applications.",
     14 	=> ": system shutdown procedure started",
     15 	=> "Starting scheduled system shutdown",
##----------------------------------------------------------------------------------------------------------------------
##----------------------------------------------------------------------------------------------------------------------
     30 	=> "UPS on Line",
     31 	=> "UPS on Standby, charging",
     32 	=> "UPS on Standby, discharging",
     33 	=> "Ups on Battery End",
     34 	=> "WARNING !! The UPS is on BATTERY. The SYSTEM will shutdown within %s seconds. Save Files, quit Applications.",
     35 	=> "WARNING !! The UPS is on BATTERY LOW. The SYSTEM will shutdown within %s seconds. Save Files, quit Applications.",
     36 	=> "UPS on battery: battery test",
     37 	=> "UPS on line: battery test OK",
     38 	=> "UPS on line: battery test KO",
     39 	=> "UPS shutdown scheduling cancelled",
     40 	=> "Output overload ended: load OK",
     41 	=> "UPS internal Temperature OK",
##----------------------------------------------------------------------------------------------------------------------
##----------------------------------------------------------------------------------------------------------------------
     60 	=> "Unable to read the UPS configuration file",
     61 	=> "UPS model not recognized",
     62 	=> "UPSEye error ",
     63 	=> "Network connection closed",
     64 	=> "New connection started with UPS at address ",
     65 	=> "Network module not configured ",
     66 	=> "UPS communication port reset",
     67 	=> "Error opening serial port %s"
     ) ;

my $VERSION      = '0.2.2';                    	# program version
my $PROGRNAME    = 'UpsEye client (Perl)' ;   	# program name

my $SHOW_HELP    = 0  ;  								# if TRUE prints help info
my $SHOW_VERSION = 0  ;  								# if TRUE prints the program version
my $DEBUG_FLAG   = 0  ;  								# if >0 prints some debug informations (
my $VERBOSE      = 0  ;  								# if TRUE, aumenta le informazioni di debugging
my $TEST_FLAG    = -1 ;  								# if TRUE apre la pagina memorizzata in questo file
my $LOGGING      = 0  ;
my $DUMP_INFO    = 0  ;  								# if TRUE the program will dump all config info

@ISA = qw(Exporter) ;
@EXPORT = qw() ;        
@EXPORT_OK = qw(ClientInit HandleSocket HandleUDP HandleSchedule PollUPS ParseCommandLine CloseSockets 
					 $INTERACTIVE $ups $SckClient $OS_win HandleExit WriteEventToLog) ;

my $Seconds ;
my $Minutes ;
my $Day ;
my $Hours ;
my $WUDay ;
my $WUHour ;
my $WUMin ;
my $RetryTimeout ;
my $SckClient ;
my $SckSelect ;
my @SckReady ;

#-----------------------------------------------------------------------------------
# used to handle the connection with the OSX driver tcp interface
#-----------------------------------------------------------------------------------
my $UDPClient ;
my $UDPSelect ;
my @UDPReady ;

my %iniTable; 
my $EventLogCounter ;
my $DataLogCounter ;
my $DataLogFH ;
my $DataSamples ;

my @EVENTS = qw(E_START E_STOP E_NOC E_COMSTART E_COMLOST E_BAT E_BLOW E_POWON 
					 E_OVERLOAD E_HTEMP E_TESTKO E_FAULT E_10TOSD E_1TOSD E_SYSSD E_SCHESD) ;

my %EventList = () ;						# event list structure

struct _EVENT =>
	{
	ID 			=> '$',					# event id
	SubID 		=> '$',					# event subid
	Enabled 		=> '$',					# 
	FireEvent	=> '$',					# the event is activated
	Notified 	=> '$',					# the event is notified to all clients
	Settings 	=> '$',					# event settings: the string is composed of five characters;
												# each character is binded to an action can assume one of the
												# following values:
												#	- 0/2: the action is disabled
												#	- 1/3: the action is enabled
												# the action are the following:
												#	- char0: log event
												#	- char1: notify event
												#	- char2: execute command file
												#	- char3: email message
												#	- char4: shutdown the system														
	RunTime		=> '$',					
	Processed	=> '$',					# number of events processed
	LogEna 		=> '$',					# log event flag
	MsgEna 		=> '$',					# notify event flag
	MsgUsers 	=> '@',					# list of the users to notify the message
	MsgRepeat	=> '$',
	MsgDelay		=> '$',
	MsgInterval	=> '$',
	CmdEna	=> '$',						# run command file flag
	CmdFile		=> '$',
	CmdDelay		=> '$',
	EmailEna 	=> '$',					# email message flag
	EmailMsg 	=> '$',					# email message
	EmailDelay 	=> '$',
	EmailUsers 	=> '@',
	ShutdownEna 	=> '$',				# system shutdown flag
	ShutdownDelay 	=> '$',
	ShutdownCmd 	=> '$',
	BlockingMode 	=> '$',				# system blocking commands
} ;


struct _CONNECTION =>
	{
	TcpPort 	=> '$',
	Server 	=> '$',
	Client 	=> '$',
	NumClients	=> '$',
	Select 	=> '$',
	InBuffer 	=> '%',
	OutBuffer 	=> '%',
	Ready 		=> '%',
	ServerAddr 	=> '$',
	ServerName 	=> '$',
	TmpBuffer   => '%', 
} ;


struct _UPSTIME =>
	{
	Day 	=> '$',
	Hour 	=> '$',
	Min 	=> '$',
	Sec 	=> '$',
} ;

#
# Scheduling structure: each array index of the defines a scheduling
#
struct _SCHEDULE =>
	{
	WeekDay 	=> '@',
	HourOn 	=> '@',
	MinOn 	=> '@',
	HourOff 	=> '@',
	MinOff 	=> '@',
	OutPort  => '@', 
	ForceUpsSD => '@',
	ForcePcSD => '@',
	SDDelay => '$'
	} ;

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
	StrStatus	=> '$',						# 
	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
	Time 			=> '_UPSTIME',				# UPS timer struct reference
	Schedule 	=> '_SCHEDULE',			# UPS schedule struct reference
	Measures 	=> '_MEASURES',			# UPS measures struct reference
} ;


#-----------------------------------------------------------------------------------
# STARTUP
#-----------------------------------------------------------------------------------
   BEGIN 
      {
      $OS_OSX = ($^O eq "darwin") ? 1 : 0 ;
      $OS_win = ($^O eq "MSWin32") ? 1 : 0 ;
      
      # This must be in a BEGIN in order for the 'use' to be conditional
      if ($OS_OSX) 
         {
         eval "use constant DETACHED_PROCESS => 0" ;   
         }
      else 
         {
         if ($OS_win)
            {
            eval "use Win32::Process" ;
            die "$@\n" if ($@) ;
            }
         else
            {
            eval "use constant DETACHED_PROCESS => 0" ;   
            die "$@\n" if ($@) ;
            }
         }
      } # End BEGIN

#-----------------------------------------------------------------------------------
# ClientInit: initializes the client program.
# It returns 1, if everything is good, 0 otherwise
#-----------------------------------------------------------------------------------
sub ClientInit (@) 
{
	my @result ;
	my $i;
	my $section ;
	
   ($INIFILE, $PROGPATH) = @_ ;
	$ups = _UPS->new ;
	$UpsSchedule = _SCHEDULE->new ;

	$ups->{PollState} = 0 ; 
	$ups->{PollInterval} = ((localtime)[0] + 2) % 60 ;
	$ups->{SckState} = UPS_SCKSTATE_INIT_MODE ; 
	$ups->{ComState} = 0 ; 
	$ups->{ErrCount} = 0 ;
	$ups->{Measures}->{Counter} = 0 ;
	$ups->{Measures}->{State} = 0x80000000 ;
	$ups->{Measures}->{OutConf} = 0 ;
	$ups->{Time}->{Day} = $ups->{Measures}->{Day} = 0 ;
	$ups->{Time}->{Hour} = $ups->{Measures}->{Hour} = 0 ;
	$ups->{Time}->{Min} = $ups->{Measures}->{Min} = 0 ;
	$ups->{Time}->{Sec} = $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 ;
		      
	($Seconds, $Minutes, $Hours) = (localtime)[0,1,2] ;
	unless (ReadIniFile(\%iniConfig,$INIFILE))
	   {
	   WriteEventToLog (eveERR_INI, 0) ;
      sleep (2) ;
	   die "Error occured while reading $INIFILE: $@";
      }

	$WUDay = 0 ;
	$WUHour = 0 ;
	$WUMin  = 0 ;

#-----------------------------------------------------------------------------------
# scheduling data
#-----------------------------------------------------------------------------------
	for ($i=0; $i<6; $i++)
		{
		$section = "Schedule" . $i ;
		$ups->{Schedule}->{WeekDay}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[0] ;
		$ups->{Schedule}->{HourOn}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[1] ;
		$ups->{Schedule}->{MinOn}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[2] ;
		$ups->{Schedule}->{HourOff}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[3] & 0x7f ;
		$ups->{Schedule}->{MinOff}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[4] ;
		$ups->{Schedule}->{OutPort}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[5] ;
		$ups->{Schedule}->{ForceUpsSD}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[6] ;
		$ups->{Schedule}->{ForcePcSD}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[7] ;
		}
	$ups->{Schedule}->{SDDelay} = $iniConfig{Schedule}{SDDelay} ;
	$ups->{Name}= $iniConfig{UpsList}{UpsModel} ;
	$ups->{Type}= $iniConfig{UpsParams}{UpsType} ;
	$ups->{Master}= $iniConfig{UpsParams}{Master} ;
	$ups->{BatteryType}= $iniConfig{UpsMDW}{TipoBat} ;
	$ups->{TScale}= $iniConfig{UpsMDW}{TempScale} ;
	$ups->{MsgFile}= $iniConfig{UpsMDW}{MsgFile} ;
	$ups->{MsgCmd}= $iniConfig{UpsMDW}{MsgCmd} ;

	$ups->{EventLog}= $iniConfig{UpsMDW}{EventLog} ;
	if ($PROGPATH)
	   {
	   $ups->{EventLog} = $PROGPATH . $ups->{EventLog}
	   }
	$ups->{ELMaxSize}= $iniConfig{UpsMDW}{ELMaxSize} ;
	$EventLogCounter = 0 ;
	$DataLogCounter = 0 ;
	$DataSamples = 0 ;
	$ups->{DataLog}= $iniConfig{UpsMDW}{DataLog} ;
	if ($PROGPATH)
	   {
	   $ups->{DataLog} = $PROGPATH . $ups->{DataLog}
	   }
	$ups->{DLMaxSize}= $iniConfig{UpsMDW}{DLMaxSize} ;
	$ups->{DelayReg}= $iniConfig{UpsMDW}{DelayReg} ;
   CheckEventLogSize ();
   CheckDataLogSize ();
   $DataLogFH = IO::File->new(">> $ups->{DataLog}") ;

   $_ = $iniConfig{UpsParams}{Language} ;
   /I/i && do     				
			{ $ups->{LangID} = 0 ;} ;
   /E/i && do     				
			{ $ups->{LangID} = 1 ;} ;
   /D/i && do     				
			{ $ups->{LangID} = 2 ;} ;
   /F/i && do     				
			{ $ups->{LangID} = 3 ;} ;
         
#-----------------------------------------------------------------------------------
# client socket initialization
#-----------------------------------------------------------------------------------
	LoadEvents() ;
	$EventList{E_START}->{FireEvent} = 1 ;
	CheckEvents ();
	$connection = _CONNECTION->new ;
	if (ConnectClient())
		{
		}
	else
		{ 
		# there is no network connection with the server
		$EventList{E_NOC}->{FireEvent} = 1 ;
   	$ups->{Measures}->{State} = 0x80000000 ;
		}  
	ConnectUDP () ;

	# check to see if i must daemonize the program
	if ($INTERACTIVE eq 0)
		{
		Demonize() ;
		}
}

#-----------------------------------------------------------------------------------
####################################################################################
# UPS COMMUNICATION
####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------
# CheckScheduling: checks if the UPS scheduling info is the same as the UPS 
# scheduling. The function is called at program startup or when the UPS 
# communication is restored.
#-----------------------------------------------------------------------------------
sub CheckScheduling()
{
	my $i;

	for ($i=0; $i<6; $i++)
		{
		if (($UpsSchedule->{WeekDay}[$i] ne $ups->{Schedule}->{WeekDay}[$i]) or
			 ($UpsSchedule->{HourOn}[$i] ne $ups->{Schedule}->{HourOn}[$i]) or
			 ($UpsSchedule->{MinOn}[$i] ne $ups->{Schedule}->{MinOn}[$i]) or
			 ($UpsSchedule->{HourOff}[$i] ne $ups->{Schedule}->{HourOff}[$i]) or
			 ($UpsSchedule->{MinOff}[$i] ne $ups->{Schedule}->{MinOff}[$i]) or
			 ($UpsSchedule->{OutPort}[$i] ne $ups->{Schedule}->{OutPort}[$i]))
			{
   		push @{$ups->{Command}},(CMD_SET_SCHEDULE,
					   					$i,
		      							$ups->{Schedule}->{WeekDay}[$i],
					      				$ups->{Schedule}->{HourOn}[$i],
					      				$ups->{Schedule}->{MinOn}[$i],
					      				$ups->{Schedule}->{HourOff}[$i],
					      				$ups->{Schedule}->{MinOff}[$i],
					      				$ups->{Schedule}->{OutPort}[$i], ":") ;

			$UpsSchedule->{WeekDay}[$i] = $ups->{Schedule}->{WeekDay}[$i] ;
		 	$UpsSchedule->{HourOn}[$i] = $ups->{Schedule}->{HourOn}[$i] ;
		 	$UpsSchedule->{MinOn}[$i] = $ups->{Schedule}->{MinOn}[$i] ;
		 	$UpsSchedule->{HourOff}[$i] = $ups->{Schedule}->{HourOff}[$i] ;
		 	$UpsSchedule->{MinOff}[$i] = $ups->{Schedule}->{MinOff}[$i] ;
		 	$UpsSchedule->{OutPort}[$i] = $ups->{Schedule}->{OutPort}[$i] ;
		}
	}
}


#-----------------------------------------------------------------------------
# HandleSchedule: checks and handles the received COMMAND
#-----------------------------------------------------------------------------
sub HandleSchedule()
{
	my $Data ;
	my ($sec,$min,$hour,$wday) = (localtime)[0,1,2,6] ;
	
	if ($Seconds != $sec)
		{
		# one second scheduling
		$Seconds = $sec ;
		if ($ups->{SckState} eq UPS_SCKSTATE_LIST_MODE)
			{
			# The communication is OK, i check for commands to send to the UPS
			my @q = () ;
			my $i = 0 ;
         if ($#{$ups->{Command}} > 0)
            {
			   while ((${$ups->{Command}}[$i] ne ":") and ($i<$#{$ups->{Command}}))
				   { $i++; }
			   if (${$ups->{Command}}[$i] eq ":")
				   {
				   @q = splice (@{$ups->{Command}}, 0, $i+1) ;
               SendTcpCommand($q[0], @q[1..@q-2]) ;
			      }
			   }
			}   				

		if ($RetryTimeout ne 0)
			{ $RetryTimeout-- ; }

   	if ($UDPClient eq -1)
      	{
		   ConnectUDP();
		   }

		CheckEvents();
		if ($ups->{SckState} eq UPS_SCKSTATE_LIST_MODE)
         { WriteDataToLog ();}

		if ($Minutes != $min)
			{
			# one minute scheduling
			$Minutes = $min ;
         if ($ups->{Master} eq 1)
			   { CheckUPSShutdown(); }
			
			if ($Hours != $hour)
				{
				# one hour scheduling
				if ($ups->{SckState} eq UPS_SCKSTATE_LIST_MODE)
					{
               if ($ups->{Master} eq 1)
                  {
			   	   my($ss,$mm,$hh, $dd) = get_time() ;
	   			   push @{$ups->{Command}},(CMD_SET_TIMER, $dd, $hh, $mm, $ss,":") ;
	   			   }
					}
				$Hours = $hour ;					
				}
			}
		}
}


#-----------------------------------------------------------------------------
# CheckUPSShutdown: checks the UPS switching OFF scheduling 
#-----------------------------------------------------------------------------
sub CheckUPSShutdown ()
{
	my $mintosd ;
	my $daymin ;
	my $dayofweek ;
	my ($i, $j, $k) ;
	my $weekmin ;
	my $result ;

	
	# i control the scheduling only if the UPS communication is OK
	if ($ups->{SckState} == UPS_SCKSTATE_LIST_MODE)
		{
		my($ss,$mm,$hh, $dd) = get_time() ;
		$weekmin = $dd * 1440 + $hh * 60 + $mm ;
		for ($i=0; $i<6; $i++)
			{
			$k = 1 ;
			for ($j=0; $j<7; $j++)
				{
				if ($ups->{Schedule}->{WeekDay}[$i] & $k)
					{
					$mintosd = $j * 1440 + $ups->{Schedule}->{HourOff}[$i] * 60 +
								  $ups->{Schedule}->{MinOff}[$i] - $weekmin	;
					if ($ups->{Schedule}->{ForcePcSD}[$i] eq 1)
						{
						# the scheduling forces the PC shutdown
						if ($mintosd eq 10)
							{
							# ... 10 minutes to shutdown enabling
							$EventList{E_10TOSD}->{FireEvent} = 1 ;
							$EventList{E_10TOSD}->{SubID} = 0 ;
							}
						elsif ($mintosd eq 1)
							{
							# ... 1 minute to shutdown enabling
							$EventList{E_1TOSD}->{FireEvent} = 1 ;
							$EventList{E_1TOSD}->{SubID} = 0 ;
							}
						elsif ($mintosd eq 0)
							{
							# ... shutdown enabled
							$EventList{E_SCHESD}->{FireEvent} = 1 ;
							$EventList{E_SCHESD}->{SubID} = 0 ;
							}
						}
					else
						{
						# the scheduling does not force the PC shutdown; it is possible
						# for Enterprise models only
						if ($ups->{Model}->{EnterpriseModel} eq 1)
							{
							if ($mintosd eq 0)
								{
								# ... i enable ONESHOT shutdown
			               $result = SendTcpCommand(CMD_SD_ONESHOT, $ups->{Schedule}->{OutPort}[$i], 0x4, 0, 1, 0, 0, 0) ;
			               if ($result eq 0)  # there were an error: send the error code!
         			         { $result = SendTcpCommand(CMD_SD_ONESHOT, $ups->{Schedule}->{OutPort}[$i], 0x4, 0, 1, 0, 0, 0) ; } 
								}
							}
						}
					}
				$k = $k * 2 ;
				}
			}
		}	
}

#-----------------------------------------------------------------------------
# CheckClientShutdown: checks the client scheduling shutdown 
#-----------------------------------------------------------------------------
sub CheckClientShutdown ()
{
	my $mintosd ;
	my $daymin ;
	my $dayofweek ;
	my ($i, $j, $k) ;
	my $weekmin ;
	my @result ;
	
	# i control the scheduling only if the UPS communication is OK
	if ($ups->{SckState} == UPS_SCKSTATE_LIST_MODE)
		{
		$weekmin = $ups->{Time}->{Day} * 1440 + $ups->{Time}->{Hour} * 60 + $ups->{Time}->{Min} ;
		for ($i=0; $i<6; $i++)
			{
			$k = 1 ;
			for ($j=0; $j<7; $j++)
				{
				if ($ups->{Schedule}->{WeekDay}[$i] & $k)
					{
					$mintosd = $j * 1440 + $ups->{Schedule}->{HourOff}[$i] * 60 +
								  $ups->{Schedule}->{MinOff}[$i] - $weekmin	;
					if ($ups->{Schedule}->{ForcePcSD}[$i] eq 1)
						{
						# the scheduling forces the PC shutdown
						if ($mintosd eq 10)
							{
							# ... 10 minutes to shutdown enabling
							$EventList{E_10TOSD}->{FireEvent} = 1 ;
							$EventList{E_10TOSD}->{SubID} = 0 ;
							}
						elsif ($mintosd eq 1)
							{
							# ... 1 minute to shutdown enabling
							$EventList{E_1TOSD}->{FireEvent} = 1 ;
							$EventList{E_1TOSD}->{SubID} = 0 ;
							}
						elsif ($mintosd eq 0)
							{
							# ... shutdown enabled
							$EventList{E_SCHESD}->{FireEvent} = 1 ;
							$EventList{E_SCHESD}->{SubID} = 0 ;
							}
						}
					}
				$k = $k * 2 ;
				}
			}
		}	
}


#-----------------------------------------------------------------------------------
####################################################################################
# EVENTS
####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# CheckEventLogSize: if the eventlog file is greater then ELMaxSize in KBytes, 
#                    tiles the file
#-----------------------------------------------------------------------------
sub CheckEventLogSize()
{
   my $neweventlog ;
   my $tmpString ;

   my $fsize = (stat($ups->{EventLog}))[7] ;
   if (!$fsize) { return -1; }
   
   if ($fsize > ($ups->{ELMaxSize} * 1024))
      {
      unless (open(EVENTLOG, "< $ups->{EventLog}")) {return 0} ;
      unless (open(NEW_EVENTLOG, "> $neweventlog")) {return 0} ;
      seek (EVENTLOG, ($fsize-$ups->{ELMaxSize} * 1024), 0) ;
      select(NEW_EVENTLOG);
      $tmpString = <EVENTLOG> ;
      while (<EVENTLOG>)
         {
         print NEW_EVENTLOG $_ ;
         }
      close (EVENTLOG) ;
      close (NEW_EVENTLOG) ;
      unlink ($ups->{EventLog}) ;
      rename ($neweventlog, $ups->{EventLog}); 
   }
}

#-----------------------------------------------------------------------------
# CheckDataLogSize: if the datalog file is greater then DLMaxSize in KBytes, 
#                   tiles the file
#-----------------------------------------------------------------------------
sub CheckDataLogSize()
{
   my $newdatalog ;
   my $tmpString ;

   if ($DataLogFH) { close($DataLogFH); }
   my $fsize = (stat($ups->{DataLog}))[7] ;
   if (!$fsize) { return 0 }
   
   if ($fsize > ($ups->{DLMaxSize} * 1024))
      {
      unless (open(LOG, "< $ups->{DataLog}")) {return 0} ;
      unless (open(NEW_LOG, "> $newdatalog")) {return 0} ;
      seek (LOG, ($fsize-$ups->{DLMaxSize} * 1024), 0) ;
      select(NEW_LOG);
      $tmpString = <LOG> ;
      while (<LOG>)
         { print NEW_LOG $_ ; }
      close (LOG) ;
      close (NEW_LOG) ;
      unlink ($ups->{DataLog}) ;
      rename ($newdatalog, $ups->{DataLog}); 
   }
}

#-----------------------------------------------------------------------------
# WriteDataToLog: writes sampled data in the datalog file
#-----------------------------------------------------------------------------
sub WriteDataToLog()
{
   my $TimeOfLog ;
   my $DataToLog ;
   my $sec; my $min; my $hour; my $mday; 
   my $mon; my $year; my $wday; my $yday; my $isdst;
   
   if (++$DataSamples != $ups->{DelayReg}) {return 0} ;
   $DataSamples = 0 ;
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
   $TimeOfLog = sprintf("%02d/%02d/%04d %02d:%02d:%02d", $mon+1, $mday, $year+1900, $hour, $min, $sec) ;
   $DataToLog = sprintf("%08X\t%02X\t%5.1f\t%5.1f\t%5.1f\t%05d\t%05d\t%05d\t%5.1f\t%5.1f\t%5.1f",
                     hex($ups->{Measures}->{State}), $ups->{Measures}->{OutConf}, 
                     $ups->{Measures}->{Vin}, $ups->{Measures}->{Vout}, $ups->{Measures}->{Vbat},
                     $ups->{Measures}->{BattPerc}, $ups->{Measures}->{Load}, $ups->{Measures}->{LoadPerc},    
                     $ups->{Measures}->{Temperature}, $ups->{Measures}->{InFreq}, $ups->{Measures}->{OutFreq} ) ;

   print $DataLogFH "$TimeOfLog $DataToLog\n" ;
   if ($DataLogCounter++ > 10000)
      {
      CheckDataLogSize () ;
      $DataLogCounter = 0 ;
      $DataLogFH = IO::File->new(">> $ups->{DataLog}") ;
      }
}


#-----------------------------------------------------------------------------
# WriteEventToLog: writes an event in the eventlog file
#-----------------------------------------------------------------------------
sub WriteEventToLog(@)
{
   my $TimeOfLog ;
   my $EventToLog ;
   my $EventDescription ;
	my ($eventID, $eventSubID) = @_ ;
   my $sec; my $min; my $hour; my $mday; 
   my $mon; my $year; my $wday; my $yday; my $isdst;
   
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
   $TimeOfLog = sprintf("%02d/%02d/%04d %02d:%02d:%02d", $mon+1, $mday, $year+1900, $hour, $min, $sec) ;
   $EventToLog = sprintf("%08d %08d %08d %08d %08d %08d", $eventID, $eventSubID, 0, 0, 0, 0) ;

	if ($eventSubID)
		{								
      if ($eventID eq eveSYSSD)
         {
   	   $EventDescription = getmessage ($eventSubID) . getmessage ($eventID) ;
         }
      else
         {
   	   $EventDescription = getmessage ($eventID) . getmessage ($eventSubID) ;
         }

	   }
   else
      {
   	$EventDescription = getmessage ($eventID) ;
      }

   unless (open(EVENTLOG, ">> $ups->{EventLog}")) {return 0} ;
   print EVENTLOG "$TimeOfLog $EventToLog \"$EventDescription\"\n" ;
   if ($eventID eq eveCOMSTART)
      {
      if ($ups->{Measures}->{State} & 0x8)
         {
         $EventToLog = sprintf("%08d %08d %08d %08d %08d %08d", eveLINEON, 0, 0, 0, 0, 0) ;
	      $EventDescription = getmessage (eveLINEON) ;
         print EVENTLOG "$TimeOfLog $EventToLog \"$EventDescription\"\n" ;
         }
      }
   close(EVENTLOG) ;
   if ($EventLogCounter++ > 1000)
      {
      CheckEventLogSize () ;
      $EventLogCounter = 0 ;
      }
}


#-----------------------------------------------------------------------------
# LoadEvents: loads the event information in the corresponding event list
#-----------------------------------------------------------------------------
sub LoadEvents()
{
	my ($event,$eventid) ;
	my $users ;
	foreach $eventid (@EVENTS)
		{
		$event = _EVENT->new ;
		$event->{ID} = $iniConfig{$eventid}{EventID} ;
		$event->{FireEvent} = 0 ;
		$event->{Processed} = 0 ;
		$event->{RunTime} = 0 ;
		$event->{Notified} = 0 ;
		$event->{SubID} = 0 ;
		$event->{Enabled} = $iniConfig{$eventid}{Enabled} ;
		$event->{Settings} = $iniConfig{$eventid}{Settings} ;
		$event->{BlockingMode} = $iniConfig{$eventid}{BlockingMode} ;
		$event->{LogEna} = substr($event->{Settings},0,1) ;
		$event->{MsgEna} = substr($event->{Settings},1,1) ;
		@{$event->{MsgUsers}} = split (/,/,$iniConfig{$eventid}{MsgUsers}) ;
		$event->{MsgRepeat} = $iniConfig{$eventid}{MsgRepeat} ;
		$event->{MsgInterval} = $iniConfig{$eventid}{MsgInterval} ;
		$event->{MsgDelay} = $iniConfig{$eventid}{MsgDelay} ;
		$event->{CmdEna} = substr($event->{Settings},2,1) ;
		$event->{CmdFile} = $iniConfig{$eventid}{CmdFile} ;
		if ($event->{BlockingMode} == 0)
			{
			# ... the command file will be forked
			if (!$OS_win)
				{
				$_ = $event->{CmdFile} ;
				s/ *$//g ;
				if ( length(chop($event->{CmdFile})) )
					{
					$event->{CmdFile} = $_ . " &" ;
					}
				}
			}
		$event->{CmdDelay} = $iniConfig{$eventid}{CmdFileDelay} ;
		$event->{EmailEna} = substr($event->{Settings},3,1) ;
		@{$event->{EmailUsers}} = split (/,/,$iniConfig{$eventid}{EmailUsers}) ;
		$event->{EmailMsg} = $iniConfig{$eventid}{EmailMsg} ;
		$event->{EmailDelay} = $iniConfig{$eventid}{EmailDelay} ;
		$event->{ShutdownEna} = substr($event->{Settings},4,1) ;
		if (($eventid eq "E_SYSSD") || ($eventid eq "E_SCHESD"))
			{
			$event->{ShutdownCmd} = $iniConfig{$eventid}{ShutdownCmd} ;
			if ($event->{BlockingMode} == 0)
				{
				# ... the command file will be forked
				if (!$OS_win)
					{
					$_ = $event->{ShutdownCmd} ;
					s/ *$//g ;
					if ( length(chop($event->{ShutdownCmd})) )
						{
						$event->{ShutdownCmd} = $_ . " &" ;
						}
					}
				}
			}
		$event->{ShutdownDelay} = $iniConfig{$eventid}{ShutdownDelay} ;
		$EventList{$eventid} = $event ;		
		}
}

#-----------------------------------------------------------------------------
# CheckUPSState: controls the UPS commutations
#-----------------------------------------------------------------------------
sub CheckUPSState ($)
{
	my $state = shift ;

	#-----------------------------------------------------------------------------
	# ... check the state if the value is changed, ...
	#-----------------------------------------------------------------------------
	
	if (($state ne $ups->{Measures}->{OldState}) || ($ups->{Measures}->{State} == 0x80000000))
		{
		if ($state & 0x80)   # UPS in Standby mode
			{
   		# ... UPS is in standby
	      if ($state & 0x01)
            {
		      NotifyEvent(eveSTDBY_DISCH) ;
   		   $ups->{StrStatus} = getmessage(eveSTDBY_DISCH);
	   	   }
		   else
		      {
   		   NotifyEvent(eveSTDBY_CHAR) ;
	   	   $ups->{StrStatus} = getmessage(eveSTDBY_CHAR);
		      }
			}
		elsif ($state & 0x01) # UPS in battery mode
			{
			if ($state & 0x04) # UPS in Battery END
				{
   			# ... ups batteries discharged
	   		NotifyEvent(eveBEND) ;
		      $ups->{StrStatus} = getmessage(eveBEND);
   		   WriteEventToLog (eveBEND, 0) ;
				}
			elsif ($state & 0x02) # UPS in Battery LOW
				{
				# ... ups low batteries charge
				$EventList{E_BLOW}->{FireEvent} = 1 ;
				$EventList{E_BLOW}->{SubID} = 0 ;
   			$ups->{StrStatus} = getmessage(eveBLOW);			
				}
			else
				{
   			$ups->{StrStatus} = getmessage(eveBMODE);
				if (not ($state & 0x10))   # Mains NOT present
					{
					# ... UPS on battery, mains off
					$EventList{E_BAT}->{FireEvent} = 1 ;
					$EventList{E_BAT}->{SubID} = 0 ;
					}
				else
					{
					# ... UPS on battery, mains on
					}
				}
			}
		else 
			{
			if ($ups->{Measures}->{State} == 0x80000000)
				{
   			# ... the UPS communication has been restored/started, the mains is OK
	   		NotifyEvent(eveLINEON) ;
		   	$ups->{StrStatus} = getmessage(eveLINEON);
				}
			else
				{
   			$EventList{E_BAT}->{FireEvent} = 0 ;
	   		$EventList{E_BLOW}->{FireEvent} = 0 ;
		   	$EventList{E_SYSSD}->{FireEvent} = 0 ;
			   $EventList{E_POWON}->{FireEvent} = 1 ;
			   $ups->{StrStatus} = getmessage(evePOWON);
				}
			}
   	if (($state & 0x1000) or ($state & 0x2000))  # temperature fault
	   	{
		   $EventList{E_HTEMP}->{FireEvent} = 1 ;
		   if ($state & 0x01)
			   {
   			$EventList{E_HTEMP}->{SubID} = eveBMODE ;
	   		$ups->{StrStatus} = getmessage(eveHTEMP).getmessage(eveBMODE);
		   	}
   		else
	   		{
		   	$EventList{E_HTEMP}->{SubID} = eveLINEON ;
			   $ups->{StrStatus} = getmessage(eveHTEMP).getmessage(eveLINEON);
   			}
	   	}
	   else
	      {
         if (($ups->{Measures}->{OldState} & 0x1000) or ($ups->{Measures}->{OldState} & 0x2000))
            {
		   	NotifyEvent(eveTEMPOK) ;
		      $ups->{StrStatus} = getmessage(eveTEMPOK);
      		WriteEventToLog (eveTEMPOK, 0) ;
            }
	      }
   	if (($state & 0x8000) or ($state & 0x4000))  # Load fault
   		{
   		$EventList{E_OVERLOAD}->{FireEvent} = 1 ;
   		if ($state & 0x01)
   			{
   			$EventList{E_OVERLOAD}->{SubID} = eveBMODE ;
   			$ups->{StrStatus} = getmessage(eveOVERLOAD).getmessage(eveBMODE);
   			}
   		else
   			{
   			$EventList{E_OVERLOAD}->{SubID} = eveLINEON ;
   			$ups->{StrStatus} = getmessage(eveOVERLOAD).getmessage(eveLINEON);
   			}
   		}
   	else
   	   {
         if (($ups->{Measures}->{OldState} & 0x8000) or ($ups->{Measures}->{OldState} & 0x4000))  
   	      {
   			NotifyEvent(eveLOADOK) ;
   		   $ups->{StrStatus} = getmessage(eveLOADOK);
      		WriteEventToLog (eveLOADOK, 0) ;
   	      }
   	   }
		}
}


#-----------------------------------------------------------------------------------
# LoadIniData: reads the new configuration data of the UPS
#-----------------------------------------------------------------------------------
sub LoadIniData
{ 
	my $i ;
	my $section ;
	for ($i=0; $i<6; $i++)
		{
		$section = "Schedule" . $i ;
		$ups->{Schedule}->{WeekDay}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[0] ;
		$ups->{Schedule}->{HourOn}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[1] ;
		$ups->{Schedule}->{MinOn}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[2] ;
		$ups->{Schedule}->{HourOff}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[3] & 0x7f ;
		$ups->{Schedule}->{MinOff}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[4] ;
		$ups->{Schedule}->{OutPort}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[5] ;
		$ups->{Schedule}->{ForceUpsSD}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[6] ;
		$ups->{Schedule}->{ForcePcSD}[$i] = (split /,/,$iniConfig{Schedule}{"$section"})[7] ;
		}

	$_ = $iniConfig{UpsParams}{Language} ;
   /I/i && do     				
			{ $ups->{LangID} = 0 ;} ;
   /E/i && do     				
			{ $ups->{LangID} = 1 ;} ;
   /D/i && do     				
			{ $ups->{LangID} = 2 ;} ;
   /F/i && do     				
			{ $ups->{LangID} = 3 ;} ;
	$ups->{Name}= $iniConfig{UpsList}{UpsModel} ;
	$ups->{Type}= $iniConfig{UpsParams}{UpsType} ;
	$ups->{BatteryType}= $iniConfig{UpsMDW}{TipoBat} ;
	$ups->{TScale}= $iniConfig{UpsMDW}{TempScale} ;
	$ups->{MsgFile}= $iniConfig{UpsMDW}{MsgFile} ;
	$ups->{MsgCmd}= $iniConfig{UpsMDW}{MsgCmd} ;
}

#-----------------------------------------------------------------------------------
# CheckEvents: handles all the events enabled and fired
#-----------------------------------------------------------------------------------
sub CheckEvents()
{
	my ($event,$interval,$module) ;
	my $eventcount ;
	my $message ;
	my @result ;
	my $Delay ;
		
	foreach $event (keys %EventList)
		{
		if (($EventList{$event}->{Enabled} == 1) && ($EventList{$event}->{FireEvent} == 1))
			{
			$eventcount = 0 ;
			#-----------------------------------------------------------------------------
			# i analize all the events fired and enabled
			#-----------------------------------------------------------------------------
			# first of all i notify the event to all clients, ...
			if ($EventList{$event}->{Notified} == 0)
				{
				$EventList{$event}->{Notified} = 1 ;
				#if ($event eq "E_SYSSD")
				#	{
				#	NotifyEvent ($EventList{$event}->{SubID},$EventList{$event}->{ID}) ;
				#	}
				#else
				#	{
					NotifyEvent ($EventList{$event}->{ID},$EventList{$event}->{SubID}) ;
				#	}
				}
			
			# ... log event section
			if (($EventList{$event}->{LogEna} == 1) && ($EventList{$event}->{RunTime} == 0))
				{
				$EventList{$event}->{Processed}++ ;
				NotifyEvent ($EventList{$event}->{ID},$EventList{$event}->{SubID}) ;
				WriteEventToLog ($EventList{$event}->{ID},$EventList{$event}->{SubID}) ;
				}
			elsif (($EventList{$event}->{LogEna} == 0) || ($EventList{$event}->{LogEna} == 2))
				{
				$eventcount++ ;
				}

			# ... message event section
			if ($EventList{$event}->{MsgEna} == 1) 
				{
				if ($EventList{$event}->{RunTime} == $EventList{$event}->{MsgDelay})
					{
					#################################
					# DA FARE: INVIO MESSAGGI POPUP #
					#################################
					if ($event eq "E_SYSSD")
						{
						$message = getmessage ($EventList{$event}->{SubID}).getmessage ($EventList{$event}->{ID}) ;
						}
					else
						{
						$message = getmessage ($EventList{$event}->{ID}) ;
						if ($EventList{$event}->{SubID})
							{								
							$message .= getmessage ($EventList{$event}->{SubID}) ;
							}
						}
					NotifyMessage ($message) ;
					if ($EventList{$event}->{MsgRepeat} == 0)
						{
						$EventList{$event}->{Processed}++ ;
						}
					}
				elsif (($EventList{$event}->{RunTime} > $EventList{$event}->{MsgDelay}) &&
						 ($EventList{$event}->{MsgRepeat} == 1))
					{
					$interval = $EventList{$event}->{RunTime} - $EventList{$event}->{MsgDelay};
					$module = $EventList{$event}->{MsgInterval} ;					
					if (($interval % $module) == 0)
						{
						#################################
						# DA FARE: INVIO MESSAGGI POPUP #
						#################################
						if ($event eq "E_BAT")
							{
							$message = getmessage (eveMSG_BMODE,
														  $EventList{$event}->{ShutdownDelay}-$EventList{$event}->{RunTime}) ;
							$EventList{$event}->{ID} = eveBMODE ;							  		
							$EventList{$event}->{SubID} = $EventList{$event}->{ShutdownDelay}-$EventList{$event}->{RunTime} ;							  		
							}
						elsif ($event eq "E_BLOW")
							{
							$message = getmessage (eveMSG_BLOW,30) ;
							}
						elsif ($event eq "E_SYSSD")
							{
							$message = getmessage ($EventList{$event}->{SubID}).getmessage ($EventList{$event}->{ID}) ;
							}
						else
							{
							$message = getmessage ($EventList{$event}->{ID}) ;
							if ($EventList{$event}->{SubID})
								{								
								$message .= getmessage ($EventList{$event}->{SubID}) ;
								}
							}												
							NotifyEvent ($EventList{$event}->{ID},$EventList{$event}->{SubID}) ;
							NotifyMessage ($message) ;
						}
					}
				}
			elsif (($EventList{$event}->{MsgEna} == 0) || ($EventList{$event}->{MsgEna} == 2))
				{
				$eventcount++ ;
				}

			# ... command file event section
			if (($EventList{$event}->{CmdEna} == 1) && 
				 ($EventList{$event}->{RunTime} == $EventList{$event}->{CmdDelay}))
				{
				RunCommand($EventList{$event}->{CmdFile}) ;
				$EventList{$event}->{Processed}++ ;
				}			
			elsif (($EventList{$event}->{CmdEna} == 0) || ($EventList{$event}->{CmdEna} == 2))
				{
				$eventcount++ ;
				}
			
			# ... email event section
			if (($EventList{$event}->{EmailEna} == 1) && 
				 ($EventList{$event}->{RunTime} == $EventList{$event}->{EmailDelay}))
				{
				#################################
				# DA IMPLEMENTARE               #
				#################################
				$EventList{$event}->{Processed}++ ;
				}
			elsif (($EventList{$event}->{EmailEna} == 0) || ($EventList{$event}->{EmailEna} == 2))
				{
				$eventcount++ ;
				}
			# ... shutdown event section
			if ((($EventList{$event}->{ShutdownEna} == 1) || ($EventList{$event}->{ShutdownEna} == 3)) && 
				 ($EventList{$event}->{RunTime} == $EventList{$event}->{ShutdownDelay}))
				{
				if ($event eq "E_BAT")
					{
					$EventList{E_BAT}->{FireEvent} = 0 ;
					$EventList{E_SYSSD}->{FireEvent} = 1 ;
					$EventList{E_SYSSD}->{SubID} = eveBMODE ;
					}
				elsif ($event eq "E_BLOW")
					{
					$EventList{E_BLOW}->{FireEvent} = 0 ;
					$EventList{E_SYSSD}->{FireEvent} = 1 ;
					$EventList{E_SYSSD}->{SubID} = eveBLOW ;
					$message = getmessage (eveMSG_BLOW,30) ;
					}
				elsif ($event eq "E_COMLOST")
					{
					$EventList{E_COMLOST}->{FireEvent} = 0 ;
					$EventList{E_SYSSD}->{FireEvent} = 1 ;
					$EventList{E_SYSSD}->{SubID} = eveCOMLOST ;
					}
				elsif ($event eq "E_SCHESD")
					{
					$Delay = $ups->{Schedule}->{SDDelay} ;
               if ($Delay < 60)
               	{ $Delay = 60 ; }
               if ($ups->{Master} eq 1)
                  {
                  my $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 1, int($Delay/256), ($Delay % 256), 0, 0, 0) ;
                  if ($result eq 0)  # there were an error: try again!
                     { $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 1, int($Delay/256), ($Delay % 256), 0, 0, 0) ; } 
					   }
					RunCommand($EventList{$event}->{ShutdownCmd}) ;
					}
				elsif ($event eq "E_SYSSD")
					{
					if ($ups->{Measures}->{State} & 0x1)
						{
						PrintDebug(1, "SYS_SD_BM \n") ;
						$Delay = $ups->{Schedule}->{SDDelay} ;
   	            if ($Delay < 60)
      	         	{ $Delay = 60 ; }
                  if ($ups->{Master} eq 1)
                     {
   	               my $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 2, int($Delay/256), ($Delay % 256), 0, 0, 0) ;
                     if ($result eq 0)  # there were an error: try again!
                        { $result = $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 2, int($Delay/256), ($Delay % 256), 0, 0, 0) ; } 
   					   }
						RunCommand($EventList{$event}->{ShutdownCmd}) ;
						}
					else
						{
						$Delay = $ups->{Schedule}->{SDDelay} ;
   	            if ($Delay < 60)
      	         	{ $Delay = 60 ; }
                  if ($ups->{Master} eq 1)
                     {
                     my $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 1, int($Delay/256), ($Delay % 256), 0, 0, 0) ;
                     if ($result eq 0)  # there were an error: try again!
                        { $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 1, int($Delay/256), ($Delay % 256), 0, 0, 0) ; } 
   					   }
						RunCommand($EventList{$event}->{ShutdownCmd}) ;
						}
					}												
				else
					{
					$Delay = $ups->{Schedule}->{SDDelay} ;
	            if ($Delay < 60)
   	         	{ $Delay = 60 ; }
               if ($ups->{Master} eq 1)
                  {
                  my $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 1, int($Delay/256), ($Delay % 256), 0, 0, 0) ;
                  if ($result eq 0)  # there were an error: try again!
                     { $result = SendTcpCommand(CMD_SD_ONESHOT, 255, 1, int($Delay/256), ($Delay % 256), 0, 0, 0) ; } 
					   }
					RunCommand($EventList{$event}->{ShutdownCmd}) ;
					}
				$EventList{$event}->{Processed}++ ;
				}
			elsif (($EventList{$event}->{ShutdownEna} == 0) || ($EventList{$event}->{ShutdownEna} == 2))
				{
				$eventcount++ ;
				}

			$EventList{$event}->{RunTime}++ ;
			# ... if all events has been notified, disable
			if (($eventcount + $EventList{$event}->{Processed}) == 5)
				{
				$EventList{$event}->{FireEvent} = 0 ;
				$EventList{$event}->{RunTime} = 0 ;
				}
			}
		else
			{
			$EventList{$event}->{Processed} = 0 ;
			$EventList{$event}->{RunTime} = 0 ;
			$EventList{$event}->{Notified} = 0 ;
			$EventList{$event}->{SubID} = 0 ;
			}
		}
}

#-----------------------------------------------------------------------------------
# NotifyEvent: notifies to all clients an UPS event
#-----------------------------------------------------------------------------------
sub NotifyEvent(@)
{
   return 0;
}

#-----------------------------------------------------------------------------------
# NotifyMessage: notifies to all clients a message
#-----------------------------------------------------------------------------------
sub NotifyMessage($)
{
	my $message = shift ;
	my @Command ;

	if (!$OS_win)
		{
	  	# if message file is != ""
  		if ($ups->{MsgFile})
    		{
		   open FH, "> $ups->{MsgFile}" ;  						# open file
 			print FH "#!/bin/sh\n" ;
		 	print FH "echo \$1 > $ups->{MsgFile}.msg\n" ;
 			print FH "$ups->{MsgCmd} < $ups->{MsgFile}.msg\n" ;
 			close(FH) ;                     						# close file
 			chmod 0777, $ups->{MsgFile} ;   						# change permissions

 			# run the command only if defined...    
 			if ($ups->{MsgCmd})      
   			{
   			   @Command = "$ups->{MsgFile} \"$message\" &" ;
   			   system(@Command) == 0 
                     or print STDERR "Unable to run command: [@Command] Err=$?\n" ;

   			# RunCommand("$ups->{MsgFile} \"$message\" &") ;
   			}
   		}
   	}    
   else
      {
	   printf ("\n Message from the UPS service\n\n\t$message \n\n");
      }
}

####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------
####################################################################################
# SOCKETS
####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# HandleSocket: listens for data on the opened server socket; if the 
# connection is not possible, retries every RetryTimeout seconds
#-----------------------------------------------------------------------------
sub HandleSocket ()
{
   my $Data ;     									# record data from UpsEye SERVER
   my @Token ;    									# contains splitted record data
   my $NumTokens ;   								# number of parameters in the message
   my $new_sock ;
   my $buf ;

	if ($SckClient eq -1)
		{
		sleep (1) ;
		# the program has lost communications with the UPS Server
		if ($SckClient ne -1)
   		{
   		close ($SckClient) ;   # communication has been lost, i close the socket
   		}
		PrintDebug(1, "Socket closed due to: communication lost or server not available\n") ;
		if ($RetryTimeout eq 0)
			{
			ConnectClient();
			}
		}
	else
		{
		@SckReady = $SckSelect->can_read(.01) ;
		foreach my $fh( @SckReady )
      	{ 
			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
      		$Data = $ARG ;    							      # is equal to: '$_'
      		PrintDebug(3, "$Data\n") ;  				      # if debugflag, I print the received data
            
      		# @Token contains the different tokens of the message (separated by ' ')
      		@Token = split(/ /, $Data) ;                 # split of the tokens
      		$NumTokens = @Token ;

      		# 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] ;
                  if (hex($tokenbuf[1]) > 0)
                  	{
	                  $ups->{Model}->{SerialNumber} = $tokenbuf[1] ;
                  	}
                  else
                  	{
	                  $ups->{Model}->{SerialNumber} = "        " ;
                  	}
                  $ups->{Model}->{NetID} = $tokenbuf[2] ;
                  $ups->{Model}->{NetName} = $tokenbuf[3] ;
         			HandleSckData(@Token) ; 
         			}
         		else
         			{	
         			HandleSckData(@Token) ; 
         			}
         		} ;
      
      		# is UpsEye SERVER sending a COMMAND?
      		/C/i && do     								# looking for 'C' message (COMMAND)
         		{ 
         		HandleSckCommand(@Token) ; 
         		} ;
      
      		# is UpsEye SERVER sending an EVENT?
      		/E/i && do     								# looking for 'E' message (EVENT)
         		{ 
         		HandleSckEvent(@Token) ; 
         		} ;      	
      		}
			else
				{
	   		close ($SckClient) ;   # communication has been lost, i close the socket
   			$SckClient = -1 ;
				if ($ups->{SckState} eq UPS_SCKSTATE_LIST_MODE)
					{
					foreach my $event (keys %EventList)
						{
						$EventList{$event}->{FireEvent} = 0 ;
						$EventList{$event}->{SubID} = 0 ;
						$EventList{$event}->{RunTime} = 0 ;
						}
   				$EventList{E_COMLOST}->{SubID} = 0 ;
					$EventList{E_COMLOST}->{FireEvent} = 1 ;
					}
				$ups->{SckState} = UPS_SCKSTATE_COMM_LOST ;
				$ups->{Measures}->{State} = 0x80000000 ;
				}	
			}
		}
}

#-----------------------------------------------------------------------------
# ConnectClient: tries to connect with the upseye server 
#-----------------------------------------------------------------------------
sub ConnectClient () 
{
	$RetryTimeout = $iniConfig{UpsParams}{RetryTimeout} ;
	$SckClient = IO::Socket::INET->new(PeerAddr => $iniConfig{UpsParams}{TcpIpAddress},
                          PeerPort => $iniConfig{UpsParams}{TcpIpPort},
                          Proto    => "tcp",
                          Type     => SOCK_STREAM,
                          Timeout  => $iniConfig{UpsParams}{TcpTimeout})
							or $SckClient = -1 ;   # was unable to open it...
	if ($SckClient ne -1)
		{
		$SckSelect = IO::Select->new() ;
		$SckSelect->add($SckClient) ;
		if (($ups->{SckState} eq UPS_SCKSTATE_INIT_MODE) or 
			 ($ups->{SckState} eq UPS_SCKSTATE_COMM_LOST))
			{
			$EventList{E_COMLOST}->{FireEvent} = 0 ;
			$EventList{E_NOC}->{FireEvent} = 0 ;
			$EventList{E_COMSTART}->{FireEvent} = 1 ;
			$EventList{E_COMSTART}->{SubID} = 0 ;
	   	my($ss,$mm,$hh, $dd) = get_time() ;
	   	push @{$ups->{Command}},(CMD_SET_TIMER, $dd, $hh, $mm, $ss,":") ;
	   	push @{$ups->{Command}},(CMD_INIT,":") ;
			$ups->{SckState} = UPS_SCKSTATE_LIST_MODE ;		
			if ($ups->{SckState} eq UPS_SCKSTATE_COMM_LOST)
				{
				ReadIniFile(\%iniConfig,$INIFILE) ;
				LoadIniData() ;
				}
			}
		return(1) ;
		}
	else
		{ return(0) ; }

}


#-----------------------------------------------------------------------------
# HandleSckData: checks and manages the received DATA
#-----------------------------------------------------------------------------
sub HandleSckData (@) 
{
   my $NumParms = @_ - 2 ;
   my $CmdID = $_[1] ;
   my @Parms = @_[2..@_-1] ;
   my $Cmp ;
   my $i ;

	$RetryTimeout = $iniConfig{UpsParams}{RetryTimeout} ;
   
   PrintDebug(4,"$NumParms [$CmdID] @Parms\n") ;
   
   $_ = $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] ;

		# in master mode, set the UPS date/time
      if ($ups->{Master} eq 1)
         {
   	   my($ss,$mm,$hh, $dd) = get_time() ;
   	   push @{$ups->{Command}},(CMD_SET_TIMER, $dd, $hh, $mm, $ss,":") ;
   	   }
      } ;
   /2002/ && do     # D-2002 IP=x.x.x.x MASK=y.y.y.y GATEWAY=z.z.z.z
      {  
		$connection->{ServerAddr} = (split(/=/,$Parms[0]))[1] ;
      } ;
   /201[0-9]/ && do # SCHEDULING
      {  
		$i = $CmdID - 2010 ;			
		if ($ups->{Master} eq 1)
			{
			# in master mode, check scheduling
			$UpsSchedule->{WeekDay}[$i] = sprintf ("%d",hex($Parms[0])) ;
			$UpsSchedule->{HourOn}[$i] = $Parms[1] ;
			$UpsSchedule->{MinOn}[$i] = $Parms[2] ;
			$UpsSchedule->{HourOff}[$i] = ($Parms[3] & 0x7F) ;
			$UpsSchedule->{MinOff}[$i] = $Parms[4] ;
			$UpsSchedule->{OutPort}[$i] = sprintf ("%d",hex($Parms[5])) ;
			$UpsSchedule->{ForceUpsSD}[$i] = (($Parms[3] & 0xf0) ? 1 : 0) ;
			if ($i eq 5)
				{ CheckScheduling(); }	
			} 
		else
			{
			$ups->{Schedule}->{WeekDay}[$i] = sprintf ("%d",hex($Parms[0])) ;
			$ups->{Schedule}->{HourOn}[$i] = $Parms[1] ;
			$ups->{Schedule}->{MinOn}[$i] = $Parms[2] ;
			$ups->{Schedule}->{HourOff}[$i] = ($Parms[3] & 0x7F) ;
			$ups->{Schedule}->{MinOff}[$i] = $Parms[4] ;
			$ups->{Schedule}->{OutPort}[$i] = sprintf("%d",hex($Parms[5])) ;
			$ups->{Schedule}->{ForceUpsSD}[$i] = $Parms[6] ;
			}
      } ;
   /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)
				{
				foreach my $event (keys %EventList)
					{
					$EventList{$event}->{FireEvent} = 0 ;
					$EventList{$event}->{SubID} = 0 ;
					$EventList{$event}->{RunTime} = 0 ;
					}
				$EventList{E_COMLOST}->{SubID} = 0 ;
				$EventList{E_COMLOST}->{FireEvent} = 1 ;
				$ups->{Measures}->{State} = 0x80000000 ;
				}
			}
		else
			{
			if ($ups->{ComState} ne 0)
				{
				$EventList{E_COMLOST}->{FireEvent} = 0 ;
				$EventList{E_COMSTART}->{FireEvent} = 1 ;
				$EventList{E_COMSTART}->{SubID} = 0 ;
				}
	      $ups->{Measures}->{OldState} = $ups->{Measures}->{State} ;   		       # save old state
	      $ups->{Measures}->{State} = $Parms[2] ;
		   if ($ups->{Measures}->{OldState} ne $ups->{Measures}->{State})
		      {
		   	CheckUPSState(hex($ups->{Measures}->{State})) ;
		      }
			$ups->{Measures}->{ShortStatus} = $Parms[3] ;
	      $ups->{Measures}->{OutConf} = $Parms[4] ;                                # OutConfig
	      $ups->{Time}->{Day} = $ups->{Measures}->{WeekDay} = $Parms[8] ;          # UPS day of week
	      $ups->{Time}->{Hour} = $ups->{Measures}->{Hour} = $Parms[9] ;            # UPS Hour
         if ($ups->{Master} eq 0)
			   { 
   	      if ($ups->{Time}->{Min} ne $Parms[10])
   	         {
   	         $ups->{Time}->{Min} = $ups->{Measures}->{Min} = $Parms[10] ;       # UPS Min
			      CheckClientShutdown(); 
			      }
			   }
	      $ups->{Time}->{Sec} = $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 (%)
      } ;
   /2200/ && do     # STATUS
      {  } ;
   /2201/ && do     # MEASURES
      {  } ;
   /2202/ && do     # CONFIG
      {  } ;
   /2203/ && do     # BATTERY LOAD+STATUS
      {  } ;
   /2207/ && do     # MASK
      {  } ;
   /2220/ && do     # SET TIMER
      {  } ;
   /2221/ && do     # BATTERY TEST
      {  } ;
   /2222/ && do     # GET BATTERY TEST
      {  } ;
   /2240/ && do     # ONESHOT
      {  } ;
   /2241/ && do     # GET ONESHOT
      {  } ;
   /2242/ && do     # SCHEDULING
      {  
      } ;
   /2243/ && do     # GET SCHEDULING
      {  
      } ;
   /2250/ && do     # ND
      {  } ;
   /2251/ && do     # ND
      {  } ;
   /2252/ && do     # ND
      {  } ;
   /2290/ && do     # UPS INIT
      {  } ;
   /2299/ && do     # REFRESH parameters
      {  
      } ;
}



#-----------------------------------------------------------------------------
# HandleSckCommand: checks and manages the received COMMANDS
#-----------------------------------------------------------------------------
sub HandleSckCommand(@)
{
   my $NumParms = @_ - 2 ;
   my $CmdID = $_[1] ;
   my @Parms = @_[2..@_] ;
   
   PrintDebug(2, "CMD:[$CmdID] @Parms\n") ;
   $RetryTimeout = $iniConfig{UpsParams}{RetryTimeout} ;

}

#-----------------------------------------------------------------------------
# CheckEvent: checks and manages the received EVENTS
#-----------------------------------------------------------------------------
sub HandleSckEvent(@)
{
   my @Parms = @_[1..@_] ;
   my $EvId ;
   my $EvSubId ;

	$EvId = $Parms[0] ;
	if ($Parms[1])
		{ $EvSubId = $Parms[1] ; }
	$RetryTimeout = $iniConfig{UpsParams}{RetryTimeout} ;

   # in SLAVE mode, i handle all the shutdown events
   if ($ups->{Master} eq 0)
      {
		if ($EvId eq eve10TOSD)
			{
			# ... 10 minutes to shutdown enabling
			$EventList{E_10TOSD}->{FireEvent} = 1 ;
			$EventList{E_10TOSD}->{SubID} = 0 ;
			}
		elsif ($EvId eq eve1TOSD)
			{
			# ... 1 minute to shutdown enabling
			$EventList{E_1TOSD}->{FireEvent} = 1 ;
			$EventList{E_1TOSD}->{SubID} = 0 ;
			}
		elsif ($EvId eq eveSCHESD)
			{
			# ... shutdown enabled
			$EventList{E_SCHESD}->{FireEvent} = 1 ;
			$EventList{E_SCHESD}->{SubID} = 0 ;
			}
		elsif ($EvId eq eveSYSSD)
			{
			# ... shutdown enabled
			$EventList{E_SYSSD}->{FireEvent} = 1 ;
			$EventList{E_SYSSD}->{SubID} = 0 ;
			}
		elsif ($EvId eq evePCOFF)
			{
			# ... shutdown enabled
			RunCommand($EventList{E_SYSSD}->{ShutdownCmd}) ;
			}

      }

}

#-----------------------------------------------------------------------------
# SendTcpCommand: in master mode, sends commands to the server (or the TCP/IP
# network module
#-----------------------------------------------------------------------------
sub SendTcpCommand (@) 
{
   my $CmdID = $_[0] ;
   my @Parms = @_[1..@_] ;
   my $sckData ;
   my $sckCommand ;
      
	if ($ups->{Master} ne 1)
		{ return(0); } ;

   $_ = $CmdID ;  # Command ID
   # analyzing the contents of '$_'

   /^0/ && do     		# STATUS
      { $sckData = sprintf "C-2200 1\r\n" ; $sckCommand = 2200; } ;
   /^1/ && do     		# MEASURES
      { $sckData = sprintf "C-2201 1\r\n" ; $sckCommand = 2201; } ;
   /^2/ && do     		# CONFIG
      { $sckData = sprintf "C-2202 1\r\n" ; $sckCommand = 2202; } ;
   /^3/ && do     		# BATTERY LOAD+STATUS
      { $sckData = sprintf "C-2203 1\r\n" ; $sckCommand = 2203; } ;
   /^7/ && do     		# MASK
      { $sckData = sprintf "C-2207 1\r\n" ; $sckCommand = 2207; } ;
   /^32/ && do     		# SET TIMER
      { 
      $sckData = sprintf "C-2220 5 %d %d %d %d\r\n", $Parms[0],$Parms[1],$Parms[2],$Parms[3] ; 
      $sckCommand = 2220 ; 
      } ;
   /^33/ && do     		# BATTERY TEST
      { 
      $sckData = sprintf "C-2221 4 %d %d %d\r\n", $Parms[0],$Parms[1],$Parms[2] ; 
      $sckCommand = 2221; 
      } ;
   /^34/ && do     		# GET BATTERY TEST
      { $sckData = sprintf "C-2222 1\r\n" ; $sckCommand = 2222; } ;
   /^64/ && do     		# SD ONESHOT
      { 
      $sckData = sprintf "C-2240 8 %d %d %d %d %d %d %d\r\n", 
      						 $Parms[0],$Parms[1],$Parms[2],$Parms[3],$Parms[4],$Parms[5],$Parms[6] ; 
      $sckCommand = 2240; 
      } ;
   /^65/ && do     		# GET SD ONESHOT
      {  } ;
   /^66/ && do     		# SCHEDULING
      { 
      $sckData = sprintf "C-2242 8 %d %d %d %d %d %d %d\r\n", 
      						 $Parms[0],$Parms[1],$Parms[2],$Parms[3],$Parms[4],$Parms[5],$Parms[6] ; 
      $sckCommand = 2242; 
      } ;
   /^67/ && do     		# GET SCHEDULING
      { 
      $sckData = sprintf "C-2243 2 %d\r\n", $Parms[0] ;
      $sckCommand = 2243 ; 
      } ;
   /^80/ && do     		# GET EEP BLOCK
      {  } ;
   /^81/ && do     		# SET EEP BLOCK
      {  } ;
   /^82/ && do     		# GET SEED
      {  } ;
   /^240/ && do     		# UPS INIT
      { $sckData = sprintf "C-2299 1\r\n" ; return (1)} ;

	if ($sckData)
		{ 
		print $SckClient $sckData ; 
		my $timeout = 0 ;
      while ($timeout < 20)
         { 
		   my @Ready = $SckSelect->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 ' ')
      		   my @Token = split(/ /, $_) ;                 # split of the tokens
               my $Data = $_ ;
      		   # 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 '$_'

         		/D/i && do     								      # looking for 'D' message (DATA)
            		{ 
                  if ($sckCommand eq $Token[1])
                     {
                     # the commands CMD_SET_TIMER, CMD_SD_ONESHOT, CMD_SET_SCHEDULE must be acknowledged
                     if (($sckCommand eq 2220) || ($sckCommand eq 2240) || ($sckCommand eq 2242))               
                        {
                        return (int($Token[5]) eq CMD_ACK) ; 
                        }
                     else
                        {                      
                        return (1) ;
                        }
         		      }
            		elsif ($Token[1] eq 2000)
            			{
            			}
            		elsif ($Token[1] eq 2001)
            			{
                     my @tokenbuf = ($Data =~ /\"(.{8})\"/gi) ;
                     $ups->{Model}->{Model} = $tokenbuf[0] ;
	                  if (hex($tokenbuf[1]) > 0)
   	               	{
	   	               $ups->{Model}->{SerialNumber} = $tokenbuf[1] ;
         	         	}
            	      else
               	   	{
	               	   $ups->{Model}->{SerialNumber} = "        " ;
                  		}
                     $ups->{Model}->{NetID} = $tokenbuf[2] ;
                     $ups->{Model}->{NetName} = $tokenbuf[3] ;
            			HandleSckData(@Token) ; 
            			}
            		else
            			{	
            			HandleSckData(@Token) ; 
            			}
            		} ;         



               }
            }
         $timeout++ ;
         }
         return (0) ;
		}
	else
	   {
	   return (1) ;
	   }
}

#-----------------------------------------------------------------------------
# NotifyClients: sends to all the program cleints, the <param> data
#-----------------------------------------------------------------------------
sub NotifyClients ($) 
{
	return 0 ;
}


####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------
####################################################################################
# READ/WRITE configuration file
####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------
# ReadIniFile (iniTable, iniFile)  
#		- iniTable: reference to the hash table used to push the INI data
#		- iniFile: INI file name
# It returns 1, if everything is good, 0 otherwise
#-----------------------------------------------------------------------------------
sub ReadIniFile {
	local *FILE ;
  	my (@ini,$section,$name,$value,$i) ;
  	my $href = $_[0] ; 		# reference to hash table
  	my $file = $_[1] ; 		# the INI file name  
  
  	foreach$i(keys%$href){delete${$href}{$i}} ;  # we clear all the hash elements
  	unless(open FILE,$file){return 0} ;
  	@ini=<FILE> ;
  	close FILE ;
  	chomp @ini ;
  	$section = '' ;
  
  	foreach $i (@ini)
  	{ 
  		while($i =~ s/^\s+|\s+$//){} ;
    	if($i =~ /^\[(.+)\]$/)
    	{ 
    		# the beginning of section  
      	$section = $1 ;
    	}
    	elsif($i =~ /^[;#]/)
    	{ 
    		# comments
    	}
    	elsif( ($i =~ /=/) && $section)
    	{ 
    		# the parameters  
      	($name,$value) = split(/=/,$i,2) ;
      	$value = $2 if($value =~ /^(["'])(.*)\1$/) ;
      	if($name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/)
      	{ 
      		# save the value in the hash table
        		$$href{$section}{$name} = $value ;
      	}
    	}
  	}
  	return 1 ;
}

#-----------------------------------------------------------------------------------
# WriteIniFile (iniTable, iniFile)  
#		- iniTable: reference to the hash table used to push the INI data
#		- iniFile: INI file name
# It returns 1, if everything is good, 0 otherwise
#-----------------------------------------------------------------------------------
sub WriteIniFile
{ 
	local *FILE ;
  	my (@ini,$section,$name,$value,$i) ;
  	my %ls ; 					# hash:  key/wrench - section, value - line  
  	my $href = $_[0] ; 		# reference to hash table  
  	my $file = $_[1] ; 		# the INI file name
  	my $brackets ;
  	
  	unless(open FILE,$file){return 0} ;
  	@ini=<FILE> ;
  	close FILE ;
  	chomp @ini ;
  	$section = '' ;

  	for($i=0;$i<=$#ini;$i++)
  	{ 
  		while($ini[$i]=~s/^\s+|\s+$//){} ;
    	if($ini[$i]=~/^\[(.+)\]$/)
    	{ 
    		# the beginning of section if remained the elements of the foregoing section
      	foreach $name(keys %{$$href{$section}})
      	{ 
      		$ini[$ls{$section}].="\n$name=\"$$href{$section}{$name}\"" ;
        		delete $$href{$section}{$name} ;
      	}
      	delete $$href{$section} ;
      	$ls{$1} = $i ;
      	$section = $1 ;
      	
      	# if the obtained section is removed   
      	$ini[$i] = '; '.$ini[$i] unless(exists $$href{$section}) ;
    	}
    	elsif($ini[$i] =~ /^[;#]/)
    	{ 
    		# comments
    	}
    	elsif( ($ini[$i]=~/=/) && $section)
    	{ 
    		# the parameters  
      	if(!exists($$href{$section}))
      	{ 
      		# section is removed - remarim all parameters  
        		$ini[$i] = '; '.$ini[$i] ;
      	}
      	else
      	{ 
      		# section is not removed - we analyze the parameters  
        		($name,$value) = split(/=/,$ini[$i],2) ;
        		$value =~ /^(["']{0,1})(.*)\1$/;
        		$value = $2 ;
        		$brackets = $1 ;
        		if($name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/)
        		{ 
        			# if $name generally exists and it does not contain left symbols
          		if($$href{$section}{$name} ne $value)
          		{ 
          			# if value of name changed or removed   
            		if(exists $$href{$section}{$name})
            		{ 
            			# value of name changed   
              			$ini[$i] = "$name=$brackets$$href{$section}{$name}$brackets" ;
              			delete $$href{$section}{$name} ;
            		}
            		else
            		{ 
            			# value of to name is removed   
              			$ini[$i] = '; '.$ini[$i] ;
            		}
          		}
          		else
          		{ 
          			# if value of name did not change   
            		delete $$href{$section}{$name} ;
          		}
        		}
        		else
        		{ 
        			# $name contain symbols different from ' A- z ', ' A- Z ', " 0-9 ' and " _ '
          		foreach$i(keys%$href){delete${$href}{$i}} ;  # we move away all elements of hash  
          		return 0 ;
        		}
      	}
    	}
  	}

  	# if the elements of last section remained   
  	foreach $name(keys %{$$href{$section}})
  	{ 
  		$ini[$ls{$section}] .= "\n$name=\"${$$href{$section}}{$name}\"" ;
    	delete $$href{$section}{$name} ;
  	}
  	delete $$href{$section} ;

  	# we add new sections  
  	foreach $section(keys %$href)
  	{ 
  		# in $section new section  
    	@ini=(@ini,"\n[$section]") ;
    	foreach $name(keys %{$$href{$section}})
    	{ 
    		@ini =(@ini,"$name=\"${$$href{$section}}{$name}\"") ;
      	delete ${$$href{$section}}{$name} ;
    	}
    	delete $$href{$section} ;
  	}

  	# we write the changed Ini- file  
  	unless(open FILE,">$file"){return 0} ;
  	foreach$i(@ini)
  	{ 	
  		print FILE "$i\n" ;
  	}
  	close FILE;
  	unless( ReadIniFile($href,$file) ){return 0} ;
  	return 1 ;
}
####################################################################################
#-----------------------------------------------------------------------------------


#-----------------------------------------------------------------------------------
####################################################################################
# UTILITY FUNCTIONS
####################################################################################
#-----------------------------------------------------------------------------------
sub get_tick_count ()
{
  	if ($OS_win) 
  	   { 
  		return Win32::GetTickCount() ;
      }
   else
      {
   	my ($real2, $user2, $system2, $cuser2, $csystem2) = POSIX::times();
   	$real2 *= 10.0;
   	return int $real2;
      }
}

#-----------------------------------------------------------------------------------
# get_time: returns the week time (i.e.: current hour, min, sec and the weekday
# starting from monday (= 0))
#-----------------------------------------------------------------------------------
sub get_time ()
{
	
	my ($sec,$min,$hour,$wday) = (localtime)[0,1,2,6] ;
	$wday = ($wday + 6) % 7 ;
   return ($sec,$min,$hour, $wday) ;
}

#-----------------------------------------------------------------------------------
# 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,
                    'version',      \$SHOW_VERSION,
                    'i',        		\$INTERACTIVE,
                    'log=s',        \$LOGGING,
                    'test=i',       \$TEST_FLAG,
                    'debug=i',      \$DEBUG_FLAG, 
                    'verbose',      \$VERBOSE,
                    'config=s',     \$INIFILE,
                    'dump',         \$DUMP_INFO,
                   );

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

    if ($SHOW_VERSION)
       {
       print "This is $PROGRNAME, version $VERSION\n\n";
       exit 0;
       }

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

Usage: UpsEye.pl [options]

   -help            : shows this help message
   -version         : shows program $PROGRNAME version
   -i          	  : upseye is interactive (not demonized)
   -test=#          : tests the event numbered '#'
   -dump            : dumps all config info
   -debug=#         : '#'=[1..4] prints debugging informations
                      higher number: more informations...
   -verbose         : shows more informations (debug mode)
   -config=File     : sets the configuration file
EofHelp
      exit 0;
      }
   
}

#-----------------------------------------------------------------------------------
# sleepex: sleeps for <Param> milliseconds
#-----------------------------------------------------------------------------------
sub sleepex($)
{
	my $timeout = get_tick_count() + shift ;
	for (;;) 
		{
		last if (get_tick_count() > $timeout) ;
		}
}

#-----------------------------------------------------------------------------
# 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};
		}
}

#-----------------------------------------------------------------------------
# PrintDebug: PrintDebug(DebugLevel, parms)  if '$DEBUG_FLAG' is set, prints 
# debug informations 
#-----------------------------------------------------------------------------
sub PrintDebug($@)
{
   my $sec; my $min; my $hour; my $mday; 
   my $mon; my $year; my $wday; my $yday; my $isdst;
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
   my $Date = sprintf "%02d/%02d/%02d-%02d:%02d:%02d", 1900+$year, $mon+1, $mday, $hour, $min, $sec ;
   
   if ($DEBUG_FLAG >= shift)  # check if DEBUG_FLAG >= DebugLevel
      {
      print "$Date\t@_" ;
      }
}

#-----------------------------------------------------------------------------
# RunCommand(Command) is a subroutine which runs the command passed as 
#           argument. 
#           Please remember: the standard 'system' command waits until the
#           completition of the spawned program. Somtimes this is not the
#           requested behaviour. The particular Windows routines used run
#           the command and immediately return to the program.
#           If you want to implement such behaviour on Unix/Linux systems,
#           you have to append an ' &' to the command, so it will be run
#           in background and the control will return to the calling program
#           immediately.
#-----------------------------------------------------------------------------
sub RunCommand($)
{
   my $Process ;
   my $Command ;      # command to run
   my $Parms = "" ;
   my @Tmp ;
   my @Cmds ;
   my $i ;


   @Cmds = split(/\\@/, $_[0]) ;

   for ($i=0 ; $i < @Cmds ; $i++)
      {
      $_ = $Cmds[$i] ;

      if ($_ eq "")
         {
         PrintDebug(1, "Command not defined!\n") ;
         return ;
         }

      s/^\s+//gi ;
      s/\s+$//gi ;
      @Tmp = split(/ /, $_) ;
      $Command = $Tmp[0] ;
      if ($OS_win)
         {
         if (@Tmp > 1)
            { 
            $Parms = join(' ', $Command, @Tmp[1..@Tmp-1]) ; 
	         }
         else
            { $Parms = $Command ; }
         }
      else
         {
         if (@Tmp > 1)
            { 
         	 $Parms = join(' ', @Tmp[1..@Tmp-1]) ;
         	 }
         else
            { $Parms = "" ; }
         }

         # if we are running this script on a Windows PC, we have to use a 
         # different function to exec a command.
         if ($OS_win)   #are we on Windows?
            {
            # i'm using a particular call to implement a "spawn"
            Win32::Process::Create($Process, 
                     $Command,
                     $Parms,
                     0,
                     DETACHED_PROCESS,
                     ".") or print STDERR "Unable to run command: $Command [$Process]\n" ;
                     # ".") || die "Create: $!" ;
                     
            }
         else  # no, we are using a "real" computer (unix, ...)
            {
            $Command .= " " . $Parms ;
            system($Command) == 0 
               or print STDERR "Unable to run command: [$Command] Err=$?\n" ;
            }
      }
}


#-----------------------------------------------------------------------------
# This function demonizes the UpsEye program
#-----------------------------------------------------------------------------
sub Demonize
{
	my $thePID ;
	
	if (!$OS_win)
		{
		open STDIN, '/dev/null' or die "Can't read /dev/null: $!" ;
		open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!" ;
		open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!" ;
		defined (my $pid = fork) or die "Can't fork: $!" ;
		exit if $pid ;
		setsid or die "Can't start a new session: $!";
		$thePID = $PROGPATH . "thepid" ;
		open(PIDFILE,">  $thePID") or die "can't create PID-File !";
		printf PIDFILE "%d\n", getpid();
		close PIDFILE;
		setsid or die "Can't start a new session: $!";
		umask 0 ;
		}
}


####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------
####################################################################################
# UDP FUNCTIONS
####################################################################################
#-----------------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# HandleUDP: handles UDP connection on port 18880
#-----------------------------------------------------------------------------
sub HandleUDP ()
{
	my $strToSend;
	my $OS_String;
	if ($UDPClient != -1)
		{
		my @Ready = $UDPSelect->can_read(.1) ;
		foreach my $fh( @Ready )
      	{ 
    		my $UDPData ;
    		$UDPClient->recv($UDPData, 512);
    		my ($rport,$ripaddr) = sockaddr_in($UDPClient->peername);
			$_ = $UDPData;
			
      	/GetInfo/i && do     								      
         	{ 
				#-----------------------------------------------------------------------------
				# If available, the infos sent will be the following:
				#		Info: this field is necessary, and represents general Company onformations
				#		MyIP: PC IP address
				#		MasterIP: Master IP address
				#		Gway: default gateway
				#		DHCP: (for tcpIP modules only)
				#		FWtcp: (for tcpIP modules only)
				#		Flags: (for tcpIP modules only)
				#		MAC: network adapter MAC address
				#		OS: operating system
				#		FW: UPS firmware release
				#		M/S: master or slave application
				#		Model: UPS model
				#		S/N: serial number
				#		nMame: PC network name
				#		ComPort: PC/UPS communication port
				#-----------------------------------------------------------------------------
				my $NetName = hostname();
   			if (($ups->{Measures}->{State} == 0x80000000) || ($ups->{ComState} != 1))
   				{
					$strToSend = sprintf("Info=Copyright 2004 - MicroDowell S.p.A.\r\nMasterIP=%s\r\nnName=%s\r\nM/S=S\r\nComPort=NET\r\n",
							      	$iniConfig{UpsParams}{TcpIpAddress},
   									$NetName);							
					}
				else
					{
					$strToSend = sprintf("Info=Copyright 2004 - MicroDowell S.p.A.\r\nMasterIP=%s\r\nFW=%s\r\nnName=%s\r\nS/N=%s\r\nModel=%s\r\nM/S=S\r\nComPort=NET\r\n",
							      	$iniConfig{UpsParams}{TcpIpAddress},
							      	$ups->{Model}->{FwMajor},
   									$NetName,							
							         $ups->{Model}->{SerialNumber},
										$ups->{Model}->{Model});
					}
				if ($^O eq "darwin")
					{
					$strToSend .= "OS=Mac OSX\r\n"
					}
 				else
 					{
					$strToSend .= "OS=Unix/Linux\r\n"
 					}
				$UDPClient->send($strToSend);
				} ;

      	/GetStatus/i && do     								      
				{ 
				#-----------------------------------------------------------------------------
				# If available, the infos sent will be the following:
				#		Status: this field is necessary, and represents the UPS status
				#		Batt: battery voltage/percent
				#		Vac: Input/output voltage
				#		Temp: UPS internal temperature
				#		Load: load and load percent
				#		Freq: input/output frequency
				#		RunT: battery run time
				#		CommOK: UPS communication status (1=OK, 0=KO)
				#-----------------------------------------------------------------------------
   			if (($ups->{Measures}->{State} == 0x80000000) || ($ups->{ComState} != 1))
   				{
	   			$strToSend = sprintf("StatusUPS=00000000\r\nBatt=%.1f %d\r\nVac=%.1f %.1f\r\nTemp=%.1f\r\nLoad=%d %d\r\nFreq=%.1f %.1f\r\nRunT=0\r\nCommOK=0\r\n",
                     				$ups->{Measures}->{State}, 
                     				$ups->{Measures}->{Vbat}, $ups->{Measures}->{BattPerc},
                     				$ups->{Measures}->{Vin}, $ups->{Measures}->{Vout},
                     				$ups->{Measures}->{Temperature}, 
                     				$ups->{Measures}->{Load}, $ups->{Measures}->{LoadPerc},    
                     				$ups->{Measures}->{InFreq},$ups->{Measures}->{OutFreq}) ;
					}
				else
					{
	   			$strToSend = sprintf("StatusUPS=%08d\r\nBatt=%.1f %d\r\nVac=%.1f %.1f\r\nTemp=%.1f\r\nLoad=%d %d\r\nFreq=%.1f %.1f\r\nRunT=0\r\nCommOK=0\r\n",
                     				$ups->{Measures}->{State}, 
                     				$ups->{Measures}->{Vbat}, $ups->{Measures}->{BattPerc},
                     				$ups->{Measures}->{Vin}, $ups->{Measures}->{Vout},
                     				$ups->{Measures}->{Temperature}, 
                     				$ups->{Measures}->{Load}, $ups->{Measures}->{LoadPerc},    
                     				$ups->{Measures}->{InFreq},$ups->{Measures}->{OutFreq}) ;
					}
   			
				$UDPClient->send($strToSend);
         	} ;

			} 
		}
}

#-----------------------------------------------------------------------------
# ConnectUDP: adds an UDP layer for the broadcast messages
#-----------------------------------------------------------------------------
sub ConnectUDP () 
{
	$UDPClient = IO::Socket::INET->new(LocalPort => 18880,
                          				  Proto    => "udp")
							or $UDPClient = -1 ;   # was unable to open it...
	if ($UDPClient ne -1)
		{
		$UDPSelect = IO::Select->new() ;
		$UDPSelect->add($UDPClient) ;
      return(1) ;
		}
	else
		{ 
		return(0) ; 
		}
}


warn "started...\n";
1 ;
