/* 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 #include #include #include #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); } } }