/* Copyright 1999 American Power Conversion, All Rights Reserved * * Description: * Implements the generic UPS * * Revision History: * mholly 19Apr1999 initial revision. * mholly 21Apr1999 hold shutoff pin for 5 sec instead of 3 * mholly 12May1999 UPSInit no longer takes the comm port param * */ #include #include #include "gnrcups.h" #include "upsreg.h" // // provide meaningful names to the comm port pins // #define LINE_FAIL MS_CTS_ON #define LOW_BATT MS_RLSD_ON #define LINE_FAIL_MASK EV_CTS #define LOW_BATT_MASK EV_RLSD // Amount of time to wait in order for the port to settle down #define DEBOUNCE_DELAY_TIME 250 // // private functions used in the implementation of the // public Generic UPS funtions // static DWORD openCommPort(LPCTSTR aCommPort); static DWORD setupCommPort(DWORD aSignalsMask); static DWORD getSignalsMask(void); static void updateUpsState(DWORD aModemStatus); static BOOL upsLineAsserted(DWORD ModemStatus, DWORD Line); static DWORD startUpsMonitoring(void); static DWORD WINAPI UpsMonitoringThread(LPVOID unused); // // UPSDRIVERCONTEXT // // provides a framework to encapsulate data that is // shared among the functions in this file // struct UPSDRIVERCONTEXT { HANDLE theCommPort; DWORD theState; HANDLE theStateChangedEvent; DWORD theSignalsMask; HANDLE theMonitoringThreadHandle; HANDLE theStopMonitoringEvent; }; // // _theUps // // provides a single instance of a UPSDRIVERCONTEXT // structure that all functions in this file will use // static struct UPSDRIVERCONTEXT _theUps; /** * GenericUPSInit * * Description: * Retrieves the UPS signalling information from the * NT Registry and attempts to open the comm port and * configure it as defined by the signalling data. * Also creates a inter-thread signal, theStateChangedEvent, * and starts the monitoring of the UPS on a separate thread * via a call to startUpsMonitoring. * The GenericUPSInit function must be called before any * other function in this file * * Parameters: * None * * Returns: * UPS_INITOK: Initalization was successful * UPS_INITREGISTRYERROR: The 'Options' registry value is corrupt * UPS_INITCOMMOPENERROR: The comm port could not be opened * UPS_INITCOMMSETUPERROR: The comm port could not be configured * UPS_INITUNKNOWNERROR: Undefined error has occurred * */ DWORD GenericUPSInit(void) { DWORD init_err = UPS_INITOK; TCHAR comm_port[MAX_PATH]; _theUps.theStateChangedEvent = NULL; _theUps.theState = UPS_ONLINE; _theUps.theCommPort = NULL; _theUps.theMonitoringThreadHandle = NULL; _theUps.theStopMonitoringEvent = NULL; if (ERROR_SUCCESS != getSignalsMask()) { init_err = UPS_INITREGISTRYERROR; goto init_end; } // // Initialize registry functions // InitUPSConfigBlock(); // // get the comm port to use // if (ERROR_SUCCESS != GetUPSConfigPort(comm_port)) { init_err = UPS_INITREGISTRYERROR; goto init_end; } if (ERROR_SUCCESS != openCommPort(comm_port)) { init_err = UPS_INITCOMMOPENERROR; goto init_end; } _theUps.theStateChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!_theUps.theStateChangedEvent) { init_err = UPS_INITUNKNOWNERROR; goto init_end; } if (ERROR_SUCCESS != setupCommPort(_theUps.theSignalsMask)) { init_err = UPS_INITCOMMSETUPERROR; goto init_end; } if (ERROR_SUCCESS != startUpsMonitoring()) { init_err = UPS_INITUNKNOWNERROR; goto init_end; } init_end: if (UPS_INITOK != init_err) { GenericUPSStop(); } return init_err; } /** * GenericUPSStop * * Description: * calls GenericUPSCancelWait to release pending threads * Stops the thread that is monitoring the UPS * Closes the comm port * Resets all data to default values * After a call to GenericUPSStop, only the GenericUPSInit * function is valid * * Parameters: * None * * Returns: * None * */ void GenericUPSStop(void) { GenericUPSCancelWait(); if (_theUps.theStopMonitoringEvent) { SetEvent(_theUps.theStopMonitoringEvent); } if (_theUps.theMonitoringThreadHandle) { WaitForSingleObject(_theUps.theMonitoringThreadHandle, INFINITE); CloseHandle(_theUps.theMonitoringThreadHandle); _theUps.theMonitoringThreadHandle = NULL; } if (_theUps.theStopMonitoringEvent) { CloseHandle(_theUps.theStopMonitoringEvent); _theUps.theStopMonitoringEvent = NULL; } if (_theUps.theCommPort) { CloseHandle(_theUps.theCommPort); _theUps.theCommPort = NULL; } if (_theUps.theStateChangedEvent) { CloseHandle(_theUps.theStateChangedEvent); _theUps.theStateChangedEvent = NULL; } _theUps.theState = UPS_ONLINE; _theUps.theSignalsMask = 0; } /** * GenericUPSWaitForStateChange * * 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 GenericUPSWaitForStateChange(DWORD aLastState, DWORD anInterval) { if (aLastState == _theUps.theState) { // // wait for a state change from the UPS // if (_theUps.theStateChangedEvent) { WaitForSingleObject(_theUps.theStateChangedEvent, anInterval); } } } /** * GenericUPSGetState * * Description: * returns the current state of the UPS * * Parameters: * None * * Returns: * possible values: * UPS_ONLINE * UPS_ONBATTERY * UPS_LOWBATTERY * UPS_NOCOMM * */ DWORD GenericUPSGetState(void) { return _theUps.theState; } /** * GenericUPSCancelWait * * Description: * interrupts pending calls to GenericUPSWaitForStateChange * without regard to timout or state change * * Parameters: * None * * Returns: * None * */ void GenericUPSCancelWait(void) { if (_theUps.theStateChangedEvent) { SetEvent(_theUps.theStateChangedEvent); } } /** * GenericUPSTurnOff * * Description: * Attempts to turnoff the UPS after the specified delay. * Simple signaling UPS do not support this feature, so * this function does nothing. * * Parameters: * aTurnOffDelay: the minimum amount of time to wait before * turning off the outlets on the UPS * * Returns: * None * */ void GenericUPSTurnOff(DWORD aTurnOffDelay) { // UPS turn off is not supported in simple mode, do nothing } /** * getSignalsMask * * Description: * Queries the registry for the 'Options' value * The 'Options' value defines a bitmask that is * used to configure the comm port settings * * Parameters: * None * * Returns: * ERROR_SUCCESS: signals mask retrieved OK * any other return code indicates failure to * retrieve the value from the registry * */ DWORD getSignalsMask(void) { DWORD status = ERROR_SUCCESS; DWORD value = 0; status = GetUPSConfigOptions(&value); if (ERROR_SUCCESS == status) { _theUps.theSignalsMask = value; } else { _theUps.theSignalsMask = 0; } return status; } /** * openCommPort * * Description: * Attempts to open the comm port * * Parameters: * aCommPort: indicates which comm port * on the system to open * * Returns: * ERROR_SUCCESS: comm port opened OK * any other return code indicates failure to * open the comm port - the exact error code * is set by the CreateFile function * */ DWORD openCommPort(LPCTSTR aCommPort) { DWORD err = ERROR_SUCCESS; _theUps.theCommPort = CreateFile( aCommPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); if (_theUps.theCommPort == INVALID_HANDLE_VALUE) { err = GetLastError(); } return err; } /** * setupCommPort * * Description: * Attempts to setup the comm port - this method * initializes the shutoff pin to an unsignalled * state, and tells the system what other pins * it should monitor for changes * * Parameters: * aSignalsMask: defines a bitmask that will * be used to configure the * comm port * * Returns: * ERROR_SUCCESS: comm port setup OK * any other return code indicates failure to * setup the comm port - the exact error code * is set by the EscapeCommFunction function * */ DWORD setupCommPort(DWORD aSignalsMask) { DWORD err = ERROR_SUCCESS; DWORD ModemStatus = 0; DWORD UpsActiveSignals = 0; DWORD UpsCommMask = 0; // // first set the 'shutoff' pin to the // unsignaled state, don't want to // shutoff the UPS now... // if (aSignalsMask & UPS_POSSIGSHUTOFF) { ModemStatus = CLRDTR; } else { ModemStatus = SETDTR; } if (!EscapeCommFunction(_theUps.theCommPort, ModemStatus)) { err = GetLastError(); } if (!EscapeCommFunction(_theUps.theCommPort, SETRTS)) { err = GetLastError(); } if (!EscapeCommFunction(_theUps.theCommPort, SETXOFF)) { err = GetLastError(); } // // determine what pins should be monitored for activity // UpsActiveSignals = (aSignalsMask & ( UPS_POWERFAILSIGNAL | UPS_LOWBATTERYSIGNAL)); switch (UpsActiveSignals) { case UPS_POWERFAILSIGNAL: UpsCommMask = LINE_FAIL_MASK; break; case UPS_LOWBATTERYSIGNAL: UpsCommMask = LOW_BATT_MASK; break; case (UPS_LOWBATTERYSIGNAL | UPS_POWERFAILSIGNAL): UpsCommMask = (LINE_FAIL_MASK | LOW_BATT_MASK); break; } // // tell the system what pins we are interested in // monitoring activity // if (!SetCommMask(_theUps.theCommPort, UpsCommMask)) { err = GetLastError(); } // // simply wait for 3 seconds for the pins to 'settle', // failure to do so results in misleading status to // be returned from GetCommModemStatus // WaitForSingleObject(_theUps.theStateChangedEvent, 3000); GetCommModemStatus( _theUps.theCommPort, &ModemStatus); updateUpsState(ModemStatus); return err; } /** * startUpsMonitoring * * Description: * Creates a separate thread to perform the monitoring * of the comm port to which the UPS is connected * Also creates an event that other threads can signal * to indicate that the monitoring thread should exit * * Parameters: * None * * Returns: * ERROR_SUCCESS: thread creation OK * any other return code indicates failure to * start the thread, either the thread was not * created or the stop event was not created * */ DWORD startUpsMonitoring() { DWORD err = ERROR_SUCCESS; DWORD thread_id = 0; _theUps.theStopMonitoringEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!_theUps.theStopMonitoringEvent) { err = GetLastError(); } else { _theUps.theMonitoringThreadHandle = CreateThread(NULL, 0, UpsMonitoringThread, NULL, 0, &thread_id); if (!_theUps.theMonitoringThreadHandle) { err = GetLastError(); } } return err; } /** * updateUpsState * * Description: * Determines the state of the UPS based upon * the state of the line fail and low battery * pins of the UPS * If the state of the UPS changes, then this * method will signal theStateChangedEvent, this * in-turn will release any threads waiting in * the GenericUPSWaitForStateChange function * * Parameters: * aModemStatus: a bitmask that represents the * state of the comm port pins, this * value should be retrieved by a call * to GetCommModemStatus * * Returns: * None * */ void updateUpsState(DWORD aModemStatus) { DWORD old_state = _theUps.theState; if (upsLineAsserted(aModemStatus, LINE_FAIL)) { if (upsLineAsserted(aModemStatus, LOW_BATT)) { _theUps.theState = UPS_LOWBATTERY; } else { _theUps.theState = UPS_ONBATTERY; } } else { _theUps.theState = UPS_ONLINE; } if (old_state != _theUps.theState) { SetEvent(_theUps.theStateChangedEvent); } } /** * upsLineAsserted * * Description: * Determines if the signal, either LINE_FAIL or LOW_BATT, * is asserted or not. The line is asserted based on the * voltage levels set in _theUps.theSignalsMask. * The aModemStatus bitmask signals a positive voltage * by a value of 1. * Below is a chart showing the logic used in determining * if a line is asserted or not * * -------------------------------------------------------------- * | UPS positive signals | UPS negative signals * -------------------------------------------------------------- * line positive | asserted | not asserted * ---------------|-----------------------|---------------------- * line negative | not asserted | asserted * ---------------|-----------------------|---------------------- * * Parameters: * aModemStatus: a bitmask that represents the * state of the comm port pins - the value * should be retrieved by a call to * GetCommModemStatus * Line: either LINE_FAIL or LOW_BATT * * Returns: * TRUE if line is asserted, FALSE otherwise * */ BOOL upsLineAsserted(DWORD aModemStatus, DWORD aLine) { DWORD asserted; DWORD status; DWORD assertion; // // only look at the line that was selected // this filters out the other fields in the // aModemStatus bitmask // status = aLine & aModemStatus; // // determine if the line is asserted based // on positive or negative voltages // assertion = (aLine == LINE_FAIL) ? (_theUps.theSignalsMask & UPS_POSSIGONPOWERFAIL) : (_theUps.theSignalsMask & UPS_POSSIGONLOWBATTERY); if (status) { // // the line has positive voltage // if (assertion) { // // the UPS uses positive voltage to // assert the line // asserted = TRUE; } else { // // the UPS uses negative voltage to // assert the line // asserted = FALSE; } } else { // // the line has negative voltage // if (assertion) { // // the UPS uses positive voltage to // assert the line // asserted = FALSE; } else { // // the UPS uses negative voltage to // assert the line // asserted = TRUE; } } return asserted; } /** * UpsMonitoringThread * * Description: * Method used by the thread to monitor the UPS port * for changes. The thread will exit when the event * _theUps.theStopMonitoringEvent is signalled * * Parameters: * unused: not used * * Returns: * ERROR_SUCCESS * */ DWORD WINAPI UpsMonitoringThread(LPVOID unused) { DWORD ModemStatus = 0; HANDLE events[2]; OVERLAPPED UpsPinOverlap; // // create an event for the OVERLAPPED struct, this event // will signalled when one of the pins that we are // monitoring, defined by SetCommMask in setupCommPort, // indicates a change in its signal state // UpsPinOverlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // // since the thread reacts to two events, a signal from the // comm port, and a Stop event, we initialize an array of events // to pass into WaitForMultipleObjects // events[0] = _theUps.theStopMonitoringEvent; events[1] = UpsPinOverlap.hEvent; while (TRUE) { // // tell the system to wait for comm events again // WaitCommEvent(_theUps.theCommPort, &ModemStatus, &UpsPinOverlap); // // block waiting for either a comm port event, or a stop // request from another thread // WaitForMultipleObjects(2, events, FALSE, INFINITE); // // Test to see if the stop event is signalled, if it // is then break out of the while loop. // // The wait is to allow for the port to settle before reading // the value. // if (WAIT_OBJECT_0 == WaitForSingleObject(_theUps.theStopMonitoringEvent, DEBOUNCE_DELAY_TIME)) { break; } // // ask the system about the status of the comm port // and pass the value to updateUpsState so it can // determine the new state of the UPS // Then simply continue monitoring the port. // GetCommModemStatus(_theUps.theCommPort, &ModemStatus); updateUpsState(ModemStatus); } CloseHandle(UpsPinOverlap.hEvent); return ERROR_SUCCESS; }