|
|
/* Copyright 1999 American Power Conversion, All Rights Reserverd
* * Description: * The ApcMiniDriver class provides an interface that is * compatible with the MiniDriver interface for the Windows2000 * UPS service. * The ApcMiniDriver makes use of a modified * PowerChute plus UPS service. This modified service has had * all of the networking, data logging, and flex manager code * removed. All that is left is the modeling and monitoring of * the connected UPS system. It is assumed that a "smart" * signalling UPS is connected. * The ApcMiniDriver class is also responsible for filling in * the advanced registry settings, battery replacement condition, * serial #, firmware rev, etc... * * Revision History: * mholly 14Apr1999 Created * mholly 16Apr1999 Convert data from UPS into wide characters * if UNICODE is defined * mholly 19Apr1999 remove registry updates of utility line state * mholly 20Apr1999 no longer updating model/vendor in registry * mholly 26Apr1999 convert RUN_TIME_REMAINING to minutes before * updating the registry - also only update the * runtime in the onRuntimeTimer method * mholly 12May1999 no longer taking aCommPort parameter in UPSInit * */
#include "cdefine.h"
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include "apcdrvr.h"
#include "apcups.h"
#include "ntsrvap.h"
#include "event.h"
#include "timerman.h"
#include "codes.h"
#include "tokenstr.h"
extern "C"{ #include "upsreg.h"
}
// Separator used for the shutdown delay allowed values
#define SHUTDOWN_DELAY_SEPARATOR ","
#define LOW_BATT_DURATION_SEPARATOR ","
#define SECONDS_TO_MINUTES 60
/**
* ApcMiniDriver * * Description: * Constructor - initializes all data members * * Parameters: * None * * Returns: * N/A * */ ApcMiniDriver::ApcMiniDriver() : theState(UPS_ONLINE), theStateChangedEvent(NULL), theReplaceBatteryState(UPS_BATTERYSTATUS_UNKNOWN), theUpsApp(NULL), theRunTimeTimer(0), theOnBatteryRuntime(-1), theBatteryCapacity(-1) { }
/**
* ~ApcMiniDriver * * Description: * Destructor - does nothing, must call * UPSStop prior to destructor * * Parameters: * None * * Returns: * N/A * */ ApcMiniDriver::~ApcMiniDriver() { }
/**
* UPSInit * * Description: * Must be the first method called on the object * Failing to call UPSInit will result in an object * in an unstable state * * Parameters: * aCommPort: not used * * Returns: * UPS_INITOK: initalized successfully * UPS_INITUNKNOWNERROR: initialization failed * */ DWORD ApcMiniDriver::UPSInit() { DWORD init_err = UPS_INITOK; theStateChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!theStateChangedEvent) { init_err = UPS_INITUNKNOWNERROR; } if ((UPS_INITOK == init_err) && (ErrNO_ERROR != initalizeUpsApplication())) { init_err = UPS_INITUNKNOWNERROR; }
if (UPS_INITOK != init_err) { UPSStop(); }
return init_err; }
/**
* UPSStop * * Description: * must be called to cleanup the object, after * this call completes the only valid method * call is UPSInit() or the destructor * * Parameters: * None * * Returns: * None * */ void ApcMiniDriver::UPSStop() { UPSCancelWait(); cleanupUpsApplication(); if (theStateChangedEvent) { CloseHandle(theStateChangedEvent); theStateChangedEvent = NULL; } }
/**
* UPSWaitForStateChange * * Description: * Blocks until the state of the UPS differs * from the value passed in via aState or * anInterval milliseconds has expired. If * anInterval has a value of INFINITE this * function will never timeout * * Parameters: * aState: defines the state to wait for a change from, * possible values: * UPS_ONLINE * UPS_ONBATTERY * UPS_LOWBATTERY * UPS_NOCOMM * * anInterval: timeout in milliseconds, or INFINITE for * no timeout interval * * Returns: * None * */ void ApcMiniDriver::UPSWaitForStateChange(DWORD aLastState, DWORD anInterval) { if (aLastState == theState) { //
// wait for a state change from the UPS
//
if (theStateChangedEvent) { WaitForSingleObject(theStateChangedEvent, anInterval); } } }
/**
* UPSGetState * * Description: * returns the current state of the UPS * * Parameters: * None * * Returns: * possible values: * UPS_ONLINE * UPS_ONBATTERY * UPS_LOWBATTERY * UPS_NOCOMM * */ DWORD ApcMiniDriver::UPSGetState() { return theState; }
/**
* UPSCancelWait * * Description: * interrupts pending calls to UPSWaitForStateChange * without regard to timout or state change * * Parameters: * None * * Returns: * None * */ void ApcMiniDriver::UPSCancelWait() { if (theStateChangedEvent) { SetEvent(theStateChangedEvent); } }
/**
* UPSTurnOff * * Description: * Attempts to turn off the outlets on the UPS * after the specified delay. This method querries the * UPS for the allowed shutdown delays and sets the * delay to one of the following: * 1. aTurnOffDelay if it exactly matches one of the allowed values * 2. the next highest value after aTurnOffDelay, if one exists * 3. the highest allowed value, if aTurnOffDelay is larger than all of * the allowed values. * If no allowed values are returned, the Shutdown Delay will not be set. * Next the UPS is instructed to sleep until power is restored. * * Parameters: * aTurnOffDelay: the minimum amount of time to wait before * turning off the outlets on the UPS * * Returns: * None * */ void ApcMiniDriver::UPSTurnOff(DWORD aTurnOffDelay) { if (theUpsApp) { char allowed_values[512];
// Retrieve the allowed shutdown delays from the UPS
theUpsApp->Get(ALLOWED_SHUTDOWN_DELAYS, allowed_values);
TokenString token_str(allowed_values, SHUTDOWN_DELAY_SEPARATOR); PCHAR tok = token_str.GetCurrentToken();
// Set the shutdown delay if there are allowed values
if (tok) { // Initialize counters
long requested_value = (long) aTurnOffDelay; long max = atol(tok); long last_closest = 0; // Cycle through the allowed values looking for a suitable value
while (tok) { long value = atol(tok);
// Check to see if this value is closest to the requested
if ( ((value >= requested_value) && (value < last_closest)) || (last_closest < requested_value)) { last_closest = value; }
// Check to see if this value is the max value
if (value > max) { max = value; }
// Get the next value
tok = token_str.GetCurrentToken(); }
long shutdown_delay = last_closest;
if (last_closest < requested_value) { // The requested value is larger than all of the values, use the max
shutdown_delay = max; }
// Set the shutdown delay
char shutdown_delay_str[4]; sprintf(shutdown_delay_str, "%3.3u", shutdown_delay); theUpsApp->Set(SHUTDOWN_DELAY, shutdown_delay_str); }
// Signal the UPS to sleep
theUpsApp->UPSTurnOff(); } }
/**
* Update * * Description: * Update is called when theUpsApp has * generated an Event for which this object * has registered. * Events are defined by a set of integer * 'codes' defined in the file 'codes.h' * * Parameters: * anEvent: a pointer to an Event object that * defines the generated event * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::Update(PEvent anEvent) { INT err = ErrNO_ERROR; if (!anEvent) { return err; } switch (anEvent->GetCode()) { case UTILITY_LINE_CONDITION: { err = onUtilityLineCondition(anEvent); } break; case BATTERY_REPLACEMENT_CONDITION: { err = onBatteryReplacementCondition(anEvent); } break; case BATTERY_CONDITION: { err = onBatteryCondition(anEvent); } break; case COMMUNICATION_STATE: { err = onCommunicationState(anEvent); } break;
case TIMER_PULSE: { err = onTimerPulse(anEvent); } break;
default: { err = UpdateObj::Update(anEvent); } break; } return err; }
/**
* onUtilityLineCondition * * Description: * Determines the current state of the power * and reports any changes to the registry and * to any threads pending on UPSWaitForStateChange * * Parameters: * anEvent: pointer to an Event object that * relates to a UTILITY_LINE_CONDITION event * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::onUtilityLineCondition(PEvent anEvent) { DWORD state = atoi(anEvent->GetValue()); DWORD old_state = theState; if (LINE_BAD == state) { theState = UPS_ONBATTERY; } else { theState = UPS_ONLINE; } if (old_state != theState) { UPSCancelWait(); } return ErrNO_ERROR; }
/**
* onBatteryReplacementCondition * * Description: * Determines the current replacement state of the * battery and reports any changes to the registry * * Parameters: * anEvent: pointer to an Event object that * relates to a BATTERY_REPLACEMENT_CONDITION event * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::onBatteryReplacementCondition(PEvent anEvent) { DWORD state = atoi(anEvent->GetValue()); DWORD old_state = theReplaceBatteryState; if (BATTERY_NEEDS_REPLACING == state) { theReplaceBatteryState = UPS_BATTERYSTATUS_REPLACE; } else { theReplaceBatteryState = UPS_BATTERYSTATUS_GOOD; } if (old_state != theReplaceBatteryState) { InitUPSStatusBlock(); SetUPSStatusBatteryStatus(theReplaceBatteryState); SaveUPSStatusBlock(FALSE); } return ErrNO_ERROR; }
/**
* onBatteryCondition * * Description: * Determines the current charge-state of the battery * and reports any changes to the registry and * to any threads pending on UPSWaitForStateChange * * Parameters: * anEvent: pointer to an Event object that * relates to a BATTERY_CONDITION event * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::onBatteryCondition(PEvent anEvent) { DWORD old_state = theState; DWORD state = atoi(anEvent->GetValue());
//
// get the current line condition
// we only goto low battery if we
// are also on battery
//
char value[256]; theUpsApp->Get(UTILITY_LINE_CONDITION, value); DWORD line_state = atoi(value); if ((BATTERY_BAD == state) || (LOW_BATTERY == state)) {
if (LINE_BAD == line_state) { theState = UPS_LOWBATTERY; } else { theState = UPS_ONLINE; } } else { if (LINE_BAD == line_state) { theState = UPS_ONBATTERY; } else { theState = UPS_ONLINE; } }
if (old_state != theState) { UPSCancelWait(); } return ErrNO_ERROR; }
/**
* onCommunicationState * * Description: * Determines the communication state to the UPS * If theState goes to UPS_NOCOMM we report the change * to threads pending on UPSWaitForStateChange * If the state is leaving UPS_NOCOMM, we * reinitialize the registry with advanced data * and then 'fake' power and battery condition * events * * Parameters: * anEvent: pointer to an Event object that * relates to a COMMUNICATION_STATE event * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::onCommunicationState(PEvent anEvent) { DWORD state = atoi(anEvent->GetValue()); if ((COMMUNICATION_LOST == state) || (COMMUNICATION_LOST_ON_BATTERY == state)) {
if (UPS_NOCOMM != theState) { theState = UPS_NOCOMM; UPSCancelWait(); } } else { if (UPS_NOCOMM == theState) { //
// need to re-initialize the UPS data, since
// when you lose comm, the user might plug in
// a new UPS system
//
initalizeAdvancedUpsData();
// Set the low battery warning threshold
setLowBatteryDuration();
//
// here we just ask the service what it thinks
// the current line/battery conditions are, and
// 'fake' an event to ourselves. This allows
// all line/battery state changes to be handled
// consistently in the same methods
//
char value[256]; theUpsApp->Get(UTILITY_LINE_CONDITION, value); Event ulc_evt(UTILITY_LINE_CONDITION, value); onUtilityLineCondition(&ulc_evt); theUpsApp->Get(BATTERY_CONDITION, value); Event batt_evt(BATTERY_CONDITION, value); onBatteryCondition(&batt_evt); } } return ErrNO_ERROR; }
/**
* onTimerPulse * * Description: * Retrieves the on-battery runtime and battery * capacity from the UPS and updates the registry * with the values if they differ from the last * time this method was called * * Parameters: * anEvent: pointer to an Event object that * relates to a TIMER_PULSE event * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::onTimerPulse(PEvent anEvent) { DWORD old_run_time = theOnBatteryRuntime; DWORD old_batt_cap = theBatteryCapacity;
//
// get the current on-battery runtime
//
CHAR data[100]; if (theUpsApp->Get(RUN_TIME_REMAINING,data) == ErrNO_ERROR) { //
// we get the RUN_TIME_REMAINING back in seconds
// the UPS applet expects the value to be in
// minutes -
theOnBatteryRuntime = atol(data) / 60; }
//
// get the current battery capacity
//
if (theUpsApp->Get(BATTERY_CAPACITY,data) == ErrNO_ERROR) { // we get the battery capacity back as a percentage
// the UPS applet expects only a whole number.
theBatteryCapacity = (long) atof(data); }
if ((old_run_time != theOnBatteryRuntime) || (old_batt_cap != theBatteryCapacity)){ // One or more values changed, update the registry
InitUPSStatusBlock();
if (old_run_time != theOnBatteryRuntime) { // Update run time remaining
SetUPSStatusRuntime(theOnBatteryRuntime); }
if (old_batt_cap != theBatteryCapacity) { SetUPSStatusBatteryCapacity(theBatteryCapacity); }
SaveUPSStatusBlock(FALSE); } //
// must create another timer to update the
// value again
//
theRunTimeTimer = _theTimerManager->SetTheTimer((ULONG)5, anEvent, this);
return ErrNO_ERROR; }
/**
* initalizeAdvancedUpsData * * Description: * Retrieves the advanced data from theUpsApp * and forces an update to the registry with * the fresh data * * Parameters: * None * * Returns: * ErrNO_ERROR * */ INT ApcMiniDriver::initalizeAdvancedUpsData() { // Initialize all static registry data
InitUPSStatusBlock(); CHAR data[100]; TCHAR w_data[200];
theUpsApp->Get(CURRENT_FIRMWARE_REV,data); #ifdef UNICODE
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, data, -1, w_data, (sizeof(w_data)/sizeof(TCHAR))); #else
strcpy(w_data, data); #endif
SetUPSStatusFirmRev(w_data);
theUpsApp->Get(UPS_SERIAL_NUMBER,data); #ifdef UNICODE
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, data, -1, w_data, (sizeof(w_data)/sizeof(TCHAR))); #else
strcpy(w_data, data); #endif
SetUPSStatusSerialNum(w_data);
// Default utility power and battery status to GOOD
SetUPSStatusBatteryStatus(UPS_BATTERYSTATUS_GOOD); SetUPSStatusBatteryCapacity(0);
SaveUPSStatusBlock(TRUE); //
// dummy up a BATTERY_REPLACEMENT_CONDITION event,
// just to make sure we initialize the registry with
// valid data, since this is an event we will not
// be notified unless the state changes
//
theUpsApp->Get(BATTERY_REPLACEMENT_CONDITION, data); Event replace_batt(BATTERY_REPLACEMENT_CONDITION, data); onBatteryReplacementCondition(&replace_batt);
return ErrNO_ERROR; }
/**
* initalizeUpsApplication * * Description: * inits theUpsApp member with a new NTServerApplication * object. * Registers for the events that can be received * in the Update method * Initalizes the advanced UPS data in the registry, * and starts the repeating event that triggers updates * to get on-battery runtime data * * Parameters: * None * * Returns: * ErrNO_ERROR: init succeeded * ErrMEMORY: theUpsApp could not be created * */ INT ApcMiniDriver::initalizeUpsApplication() { INT err = ErrNO_ERROR;
//
// assume that the UPS is not on battery
// this matches the assumption made by
// the NTServerApplication class
//
theState = UPS_ONLINE; theUpsApp = new NTServerApplication(); if (!theUpsApp) { err = ErrMEMORY; } else { theUpsApp->RegisterEvent(UTILITY_LINE_CONDITION, this); theUpsApp->RegisterEvent(BATTERY_CONDITION, this); theUpsApp->RegisterEvent(BATTERY_REPLACEMENT_CONDITION, this); theUpsApp->RegisterEvent(COMMUNICATION_STATE, this); err = theUpsApp->Start(); } if (ErrNO_ERROR == err) { initalizeAdvancedUpsData(); // Start the polling of UPS run time
if (!theRunTimeTimer) { Event retry_event(TIMER_PULSE, TIMER_PULSE); onTimerPulse(&retry_event); } } return err; }
/**
* cleanupUpsApplication * * Description: * cleans up and destructs theUpsApp object. Sets * member variables to the default values * * * Parameters: * None * * Returns: * None * */ void ApcMiniDriver::cleanupUpsApplication() { theState = UPS_ONLINE;
if (theUpsApp) { theUpsApp->Quit(); delete theUpsApp; theUpsApp = NULL; } theRunTimeTimer = 0; theOnBatteryRuntime = -1; }
/**
* setLowBatteryDuration * * Description: * Sets the low battery duration to a value compatable with the * UPSTurnOffWait registry key. This method querries the * UPS for the allowed low battery durations and sets the * duration to one of the following: * 1. The value of UPSTurnOffWait if it exactly matches one of * the allowed values * 2. the next highest value after UPSTurnOffWait, if one exists * 3. the highest allowed value, if UPSTurnOffWait is larger than * all of the allowed values. * If the UPSTurnOffWait is not set or no allowed values are returned, * the low battery duration will not be set. * * Parameters: * None * * Returns: * None * */ void ApcMiniDriver::setLowBatteryDuration() { DWORD turn_off_wait;
InitUPSConfigBlock();
if ((GetUPSConfigTurnOffWait(&turn_off_wait) == ERROR_SUCCESS) && (theUpsApp)) { char allowed_values[512]; // Retrieve the allowed low battery durations from the UPS
theUpsApp->Get(ALLOWED_LOW_BATTERY_DURATIONS, allowed_values); TokenString token_str(allowed_values, LOW_BATT_DURATION_SEPARATOR); PCHAR tok = token_str.GetCurrentToken(); // Set the low battery duration if there are allowed values
if (tok) { // Initialize counters
long max = atol(tok); long last_closest = 0; long requested_value = 0; if (turn_off_wait > 0) { // Convert the value from seconds to minutes (rounded up)
requested_value = turn_off_wait / SECONDS_TO_MINUTES; if ((turn_off_wait % SECONDS_TO_MINUTES) > 0) { ++requested_value; } } // Cycle through the allowed values looking for a suitable value
while (tok) { long value = atol(tok); // Check to see if this value is closest to the requested
if ( ((value >= requested_value) && (value < last_closest)) || (last_closest < requested_value)) { last_closest = value; } // Check to see if this value is the max value
if (value > max) { max = value; } // Get the next value
tok = token_str.GetCurrentToken(); } long low_batt_duration = last_closest; if (last_closest < requested_value) { // The requested value is larger than all of the values, use the max
low_batt_duration = max; } // Set the shutdown delay
char low_batt_duration_str[4]; sprintf(low_batt_duration_str, "%2.2u", low_batt_duration); theUpsApp->Set(LOW_BATTERY_DURATION, low_batt_duration_str); } } }
|