/*
 * $Id: hid-ups.c,v 1.13.2.2 2007-07-17 22:54:25 adk0212 Exp $
 *
 *  Copyright (c) 2001 Vojtech Pavlik <vojtech@ucw.cz>
 *  Copyright (c) 2001 Paul Stewart <hiddev@wetlogic.net>
 *
 *    Tweaked by Kern Sibbald <kern@sibbald.com> to learn
 *    about USB UPSes.			      
 *
 *  HID UPS device test program
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 * 
 * Should you need to contact me, the author, you can do so either by
 * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
 * Vojtech Pavlik, Simunkova 1492, Prague 8, 182 00 Czech Republic
 */

#define DEBUG 1 		      /* if set prints full reports */
#define TESTING 1		      /* if set disables actual operation */

#define HID_MAX_USAGES 1024

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/signal.h>
#include <asm/types.h>
#include <linux/hiddev.h>
#include <errno.h>
#include <time.h>

#define PWRSTAT         "/etc/powerstatus"
#define DEBOUNCE_TIMEOUT 15	      /* increase this if you get false alerts */

#define UPS_USAGE		0x840000
#define UPS_SERIAL		0x8400fe
#define BAT_CHEMISTRY		0x850089
#define UPS_CAPACITY_MODE	0x85002c

#define UPS_SHUTDOWN_IMMINENT	0x840069
#define UPS_BATTERY_VOLTAGE	0x840030
#define UPS_BELOW_RCL		0x840042
#define UPS_CHARING		0x840044
#define UPS_DISCHARGING 	0x850045
#define UPS_REMAINING_CAPACITY	0x850066
#define UPS_RUNTIME_TO_EMPTY	0x850068
#define UPS_AC_PRESENT		0x8500d0

#define STATE_NORMAL 0		      /* unit powered */
#define STATE_DEBOUNCE 1	      /* power failure */
#define STATE_BATTERY 2 	      /* power failure confirmed */

/* USB Vendor ID's */
#define Vendor_APC 0x51D
#define Vendor_MGE 0x463


#ifdef DEBUG
/*
 *    The type field indicates the type or units to be
 *    applied to the value.
 */

#define T_NONE	   0		      /* No units */
#define T_INDEX    1		      /* String index */
#define T_CAPACITY 2		      /* Capacity (usually %) */
#define T_BITS	   3		      /* bit field */
#define T_UNITS    4		      /* use units/exponent field */
#define T_DATE	   5		      /* date */
#define T_APCDATE  6		      /* APC date format */

struct s_ups_info {
    unsigned usage;
    int type;
    const char *label;
} ups_info[] = {

    /* MGE & APC */
    { 0x000000, T_NONE,    "None" },
    /* Page 0x84 is the Power Device Page */
    { 0x840000, T_NONE,    "UPS-Power" },
    { 0x840001, T_INDEX,   "iName" },
    { 0x840002, T_NONE,    "PresentStatus" },
    { 0x840004, T_NONE,    "UPS" },
    { 0x840012, T_NONE,    "Battery" },
    { 0x840016, T_NONE,    "PowerConverter" },
    { 0x840018, T_NONE,    "OutletSystem" },
    { 0x840017, T_NONE,    "PowerConverterID" },
    { 0x840019, T_NONE,    "OutletSystemID" },
    { 0x84001a, T_NONE,    "Input" },
    { 0x84001c, T_NONE,    "Output" },
    { 0x84001e, T_NONE,    "Flow" }, 
    { 0x84001d, T_NONE,    "OutputID" },
    { 0x84001f, T_NONE,    "FlowID" },
    { 0x840020, T_NONE,    "Outlet" },
    { 0x840021, T_NONE,    "OutletID" },
    { 0x840024, T_NONE,    "PowerSummary" },
    { 0x840025, T_NONE,    "PowerSummaryID" },
    { 0x840030, T_UNITS,   "Voltage" },
    { 0x840031, T_UNITS,   "Current" },
    { 0x840032, T_UNITS,   "Frequency" },
    { 0x840033, T_UNITS,   "ApparentPower" },
    { 0x840034, T_UNITS,   "ActivePower" },
    { 0x840035, T_UNITS,   "PercentLoad" },
    { 0x840036, T_UNITS,   "Temperature" },
    { 0x840037, T_UNITS,   "Humidity" },
    { 0x840040, T_UNITS,   "ConfigVoltage" },
    { 0x840042, T_UNITS,   "ConfigFrequency" },
    { 0x840043, T_UNITS,   "ConfigApparentPower" },
    { 0x840044, T_UNITS,   "ConfigActivePower" },
    { 0x840053, T_UNITS,   "LowVoltageTransfer" },
    { 0x840054, T_UNITS,   "HighVoltageTransfer" },
    { 0x840055, T_UNITS,   "DelayBeforeReboot" },
    { 0x840056, T_UNITS,   "DelayBeforeStartup" },
    { 0x840057, T_NONE,    "DelayBeforeShutdown" },
    { 0x840058, T_NONE,    "Test" },
    { 0x84005a, T_NONE,    "AudibleAlarmControl" },
    { 0x840061, T_NONE,    "Good" },
    { 0x840062, T_NONE,    "InternalFailure" },
    { 0x840065, T_NONE,    "Overload" },
    { 0x840068, T_NONE,    "ShutdownRequested" },
    { 0x840069, T_NONE,    "ShutdownImminent" },
    { 0x84006b, T_NONE,    "Switch On/Off" },
    { 0x84006c, T_NONE,    "Switchable" },
    { 0x84006e, T_NONE,    "Boost" },
    { 0x84006f, T_NONE,    "Trim" },
    { 0x840073, T_NONE,    "CommunicationLost" },
    { 0x8400fd, T_INDEX,   "iManufacturer" },
    { 0x8400fe, T_INDEX,   "iProduct" },
    { 0x8400ff, T_INDEX,   "iSerialNumber" },
    /* Page 0x85 is the Battery System Page */
    { 0x850000, T_NONE,     "UPS-Batt" },
    { 0x850029, T_CAPACITY, "RemainingCapacityLimit" },
    { 0x85002a, T_UNITS,    "RemainingTimeLimit" },
    { 0x85002c, T_CAPACITY, "CapacityMode" },
    { 0x850042, T_NONE,     "BelowRemainingCapacityLimit" },
    { 0x850043, T_NONE,     "RemainingTimeLimitExpired" },
    { 0x850044, T_NONE,     "Charging" },
    { 0x850045, T_NONE,     "Discharging" },
    { 0x85004b, T_NONE,     "NeedReplacement" },
    { 0x850058, T_NONE,     "BUPHibernate" },           /* APC proprietary */
    { 0x850066, T_CAPACITY, "RemainingCapacity" },
    { 0x850067, T_CAPACITY, "FullChargeCapacity" },
    { 0x850068, T_UNITS,    "RunTimeToEmpty" },
    { 0x85006b, T_NONE,     "CycleCount" },
    { 0x850080, T_NONE,     "BattPackLevel" },
    { 0x850083, T_CAPACITY, "DesignCapacity" },
    { 0x850085, T_DATE,     "ManufactureDate" },
    { 0x850088, T_INDEX,    "iDeviceName" },
    { 0x850089, T_INDEX,    "iDeviceChemistry" },
    { 0x85008b, T_NONE,     "Rechargeable" },
    { 0x85008c, T_CAPACITY, "WarningCapacityLimit" },
    { 0x85008d, T_CAPACITY, "CapacityGranularity1" },
    { 0x85008e, T_CAPACITY, "CapacityGranularity2" },
    { 0x85008f, T_INDEX,    "iOEMInformation" },
    { 0x8500d0, T_NONE,     "ACPresent" },
    { 0x8500d1, T_NONE,     "BatteryPresent" },
    { 0x8500db, T_NONE,     "VoltageNotRegulated" },
    /*
     * Page 0x86 is reserved for Power Devices, but not defined in the HID
     * standard. APC has defined a few usages on this page for themselves.
     */
    { 0x860010, T_NONE,     "BUPSelfTest" },             /* APC proprietary */
    { 0x860012, T_NONE,     "BUPBattCapBeforeStartup" }, /* APC proprietary */
    { 0x860076, T_NONE,     "BUPDelayBeforeStartup" },   /* APC proprietary */
    /* Pages 0xFF00 to 0xFFFF are vendor specific */
    { 0xFF860005, T_NONE,   "APCGeneralCollection" },
    { 0xFF860013, T_NONE,   "APC860013_SetMinReturn?" },
    { 0xFF860016, T_APCDATE,"APCBattReplacementDate" },
    { 0xFF860019, T_UNITS,  "APCBattCapBeforeStartup" },
    { 0xFF860023, T_NONE,   "APC860023_??????" },
    { 0xFF860024, T_NONE,   "APC860024_??????" },
    { 0xFF860025, T_NONE,   "APC860025_??????" },
    { 0xFF860026, T_NONE,   "APC860026_??????" },
    { 0xFF860029, T_NONE,   "APC860029_??????" },
    { 0xFF86002A, T_NONE,   "APC86002A_??????" },
    { 0xFF860042, T_INDEX,  "APC_UPS_FirmwareRevision" },
    { 0xFF860052, T_NONE,   "APCLineFailCause" },
    { 0xFF860060, T_BITS,   "APCStatusFlag" },
    { 0xFF860061, T_NONE,   "APCSensitivity" },
    { 0xFF860062, T_NONE,   "APC860062_SetHiTransV?" }, 
    { 0xFF860064, T_NONE,   "APC860064_SetLoTransV?" },
    { 0xFF860072, T_NONE,   "APCPanelTest" },
    { 0xFF860074, T_NONE,   "APC860074_SetSens?" },
    { 0xFF860076, T_UNITS,  "APCShutdownAfterDelay" },
    { 0xFF860077, T_NONE,   "APC860077_SetWakeUpDelay?" },
    { 0xFF860079, T_INDEX,  "APC_USB_FirmwareRevision" },
    { 0xFF86007C, T_NONE,   "APCForceShutdown" },
    { 0xFF86007D, T_UNITS,  "APCDelayBeforeShutdown" },
    { 0xFF86007E, T_UNITS,  "APCDelayBeforeStartup" },
    { 0xFF8600FC, T_UNITS,  "ModbusRTURx" },
    { 0xFF8600FD, T_UNITS,  "ModbusRTUTx" },

};
#define UPS_INFO_SZ (sizeof(ups_info)/sizeof(ups_info[0]))

const char *reports[] = { "Unknown", "Input", "Output", "Feature" };

static int CapacityMode = 2;	      /* default = % */

char unknown[24];

#define MADDR "stewart@wetlogic.net"

void log_status(const char *msg) {
#ifndef TESTING
    char buf[256];
    printf("[Log message \"%s\"]\n", msg);
    sprintf(buf, "/bin/echo %s | /bin/mail -s \"UPS System\" %s", msg, MADDR);
    system(buf);
#else
    printf("[Log message \"%s\"]\n", msg);
#endif
}

static const char* info(unsigned int detail) {
    unsigned int i;
    
    for (i = 0; i < UPS_INFO_SZ; i++) {
        if (ups_info[i].usage == detail) {
            return ups_info[i].label;
        }
    }

    sprintf(unknown, "[%06x]", detail);

    return unknown;
}

static struct s_ups_info *info_entry(unsigned int detail) {
    unsigned int i;
    static struct s_ups_info info = {0, T_NONE, unknown};
    
    for (i = 0; i < UPS_INFO_SZ; i++) {
        if (ups_info[i].usage == detail) {
            return &ups_info[i];
        }
    }
    sprintf(unknown, "[%06x]", detail);
    return &info;    
}

#else /* DEBUG */
#define log_status(s)
#endif /* DEBUG */

/* Tell init the power has either gone or is back. */
void powerfail(int state) {
#ifndef TESTING
    int fd;
    
    /* Create an info file needed by init to shutdown/cancel shutdown */
    unlink(PWRSTAT);
    if ((fd = open(PWRSTAT, O_CREAT|O_WRONLY, 0644)) >= 0) {
        if (state > 0)
            write(fd, "FAIL\n", 5);
        else if (state < 0)
            write(fd, "LOW\n", 4);
        else
            write(fd, "OK\n", 3);
        close(fd);
    }
    kill(1, SIGPWR);
#else
    printf("We are in powerfail() with state=%d ", state);
    if (state > 0)
        printf("POWER FAILURE\n");
    else if (state < 0)
        printf("BATTERY LOW\n");
    else
        printf("OK\n");
#endif
    
}

static inline int find_application(int fd, unsigned usage) {
        int i = 0, ret;
        while ((ret = ioctl(fd, HIDIOCAPPLICATION, i)) > 0 &&
               (ret & 0xffff0000) != (usage & 0xffff0000)) i++;
        return ((ret & 0xffff000) == (usage & 0xffff0000));
}

/*
 * Get a string from the device given the string's index
 */
static const char *get_string(int fd, int sindex) {
    static struct hiddev_string_descriptor sdesc;
    static char buf[200];

    if (sindex == 0) {
       return "";
    }
    sdesc.index = sindex;
    if (ioctl(fd, HIDIOCGSTRING, &sdesc) < 0) {
        sprintf(buf, "String index %d returned ERR=%s\n", sindex,
            strerror(errno));
        return buf;
    }
    return sdesc.value;
}

/*
 *   Give units code and exponent, returns string 
 *    describing the units used.  (doesn't work for percentages).
 */
static const char *get_units(unsigned unit, int exponent) {
    static char buf[200];

    if (exponent > 7) {
       exponent = exponent - 16;
    }
    switch (unit) {
    case 1:			      /* special kludge for CapacityMode */
        switch (exponent) {
        case 0:
           return "maH";
        case 1:
           return "mwH";
        case 2:
           return "percent";
        case 3:
           return "boolean";
        default:
           return "";
        }
    case 0x00F0D121:
        if (exponent == 7) {
           return "Volts";
        } else if (exponent == 5) {
           return "CentiVolts";
        } else if (exponent == 6) {
           return "DeciVolts";
        } else {
           sprintf(buf, "Volts with %d exponent", exponent);
           return buf;
        }
    case 0x00100001:
        if (exponent == -2) {
           return "CentiAmps";
        } else if (exponent == 0) {
           return "Amps";
        } else {
           sprintf(buf, "Amps with %d exponent", exponent);
           return buf;
        }
    case 0xF001:
        if (exponent == 0) {
           return "Hertz";
        } else if (exponent == -2) {
           return "CentiHertz";
        } else {
           sprintf(buf, "Hertz with %d exponent", exponent);
           return buf;
        }
    case 0x1001:
        if (exponent == 0) {
           return "Seconds";
        } else {
           sprintf(buf, "Seconds with %d exponent", exponent);
           return buf;
        }
    case 0xD121:
        return "Watts";
    case 0x010001:
        if (exponent == 0) {
           return "Degrees K";
        } else {
           sprintf(buf, "Degrees K with %d exponent", exponent);
           return buf;
        }
    case 0x0101001:
        return "AmpSecs";
    case 0:
        return "";
    default:
        sprintf(buf, "0x%x", unit);
        return buf;
    }
}

       
static char evdev[50];

static int vendor = 0;

int main (int argc, char **argv) {
    time_t start_seconds;
    int fd = -1, rd, j, RemainingCapacity;
    unsigned int i;
    struct hiddev_event ev[64];
    struct hiddev_devinfo dinfo;
    char name[256] = "Unknown";
    int state = 0;
    fd_set fdset;
    struct timeval timev, *tv = NULL;
    struct hiddev_usage_ref uref;

    if (argc < 2) {
         /* deal with either standard location or Red Hat's */
         const char *hid_dirs[] = {"/dev/usb/hid", "/dev/usb","/dev"};
         for (i = 0; i < sizeof(hid_dirs)/sizeof(hid_dirs[0]); i++) {
             for (j = 0; j < 4; j++) {
                 sprintf(evdev, "%s/hiddev%d", hid_dirs[i], j);
                 if ((fd = open(evdev, O_RDONLY)) < 0) {
                     if (errno == EACCES) {
                         fprintf(stderr, "No permission, try this as root.\n");
                         exit(1);
                     }
                 } else {
                     if (find_application(fd, UPS_USAGE)) goto foundit;
                     close(fd);
                 }
              }
          }
          fprintf(stderr, "Couldn't find USB UPS device, check your /dev.\n");
          exit(1);
foundit:
          printf("Found UPS at %s\n", evdev);
      } else {
           strncpy(evdev, argv[argc -1], sizeof(evdev)-1);
           printf("Found UPS at %s\n", evdev);
        if ((fd = open(evdev, O_RDONLY)) < 0) {
            perror("hiddev open");
            exit(1);
        }
        if (!find_application(fd, UPS_USAGE)) {
            fprintf(stderr, "%s is not a UPS\n", argv[argc - 1]);
            exit(1);
        }
    }

#ifdef DEBUG
    {
        unsigned version;

        ioctl(fd, HIDIOCGVERSION, &version);
        printf("hiddev driver version is %d.%d.%d\n",
               version >> 16, (version >> 8) & 0xff, version & 0xff);
        
        ioctl(fd, HIDIOCGDEVINFO, &dinfo);
        printf("HID: vendor 0x%x product 0x%x version 0x%x ",
               dinfo.vendor, dinfo.product & 0xffff, dinfo.version);
        printf("app %s", info(ioctl(fd, HIDIOCAPPLICATION, 0)));
        for (i = 1; i < dinfo.num_applications; i++)
            printf(", %s", info(ioctl(fd, HIDIOCAPPLICATION, i)));
        printf("\n");
        printf("HID: bus: %d devnum: %d ifnum: %d\n",
               dinfo.busnum, dinfo.devnum, dinfo.ifnum);
        vendor = dinfo.vendor;
                   
    }
#endif

    ioctl(fd, HIDIOCINITREPORT, 0);
    ioctl(fd, HIDIOCGNAME(sizeof(name)), name);
    printf("UPS HID device name: \"%s\"\n", name);


    memset(&uref, 0, sizeof(uref));
    uref.report_type = HID_REPORT_TYPE_FEATURE;
    uref.report_id = HID_REPORT_ID_UNKNOWN;
    uref.usage_code = BAT_CHEMISTRY;
    if (ioctl(fd, HIDIOCGUSAGE, &uref) == 0) {
        printf("Battery Chemistry: \"%s\" (%d)\n", get_string(fd, uref.value), 
            uref.value);
    }

    memset(&uref, 0, sizeof(uref));
    uref.report_type = HID_REPORT_TYPE_FEATURE;
    uref.report_id = HID_REPORT_ID_UNKNOWN;
    uref.usage_code = UPS_CAPACITY_MODE;
    if (ioctl(fd, HIDIOCGUSAGE, &uref) == 0) {
        CapacityMode = uref.value;
    }

#ifdef DEBUG
    /* To traverse the report descriptor info
     */

    {
        struct hiddev_report_info rinfo;
        struct hiddev_field_info finfo;
        struct hiddev_usage_ref uref;
        int rtype;
        unsigned int i, j;

        for (rtype = HID_REPORT_TYPE_MIN; rtype <= HID_REPORT_TYPE_MAX;
             rtype++) {
            rinfo.report_type = rtype;
            rinfo.report_id = HID_REPORT_ID_FIRST;
            while (ioctl(fd, HIDIOCGREPORTINFO, &rinfo) >= 0) {
                printf("\n%sReport %d\n",
                       reports[rinfo.report_type], rinfo.report_id);

                for (i = 0; i < rinfo.num_fields; i++) { 
                    struct s_ups_info *p;

                    memset(&finfo, 0, sizeof(finfo));
                    finfo.report_type = rinfo.report_type;
                    finfo.report_id = rinfo.report_id;
                    finfo.field_index = i;
                    ioctl(fd, HIDIOCGFIELDINFO, &finfo);
                    printf("  Field %d, app %s, phys %s, log %s\n", 
                            i, 
                            info(finfo.application), info(finfo.physical),
                            info(finfo.logical));

                    memset(&uref, 0, sizeof(uref));
                    for (j = 0; j < finfo.maxusage; j++) {
                        unsigned unit;
                        int exponent;
                        int v, yy, mm, dd;
                        uref.report_type = finfo.report_type;
                        uref.report_id = finfo.report_id;
                        uref.field_index = i;
                        uref.usage_index = j;
                        ioctl(fd, HIDIOCGUCODE, &uref);
                        ioctl(fd, HIDIOCGUSAGE, &uref);
                        p = info_entry(uref.usage_code);
                        switch (p->type) {
                        case T_CAPACITY:
                           unit = 1;
                           printf("Exponent %d lost.\n", finfo.unit_exponent);
                           exponent = CapacityMode;
                           break;
                        case T_UNITS:
                           unit = finfo.unit;
                           exponent = finfo.unit_exponent;
                           break;
                        default:
                           unit = 0;
                           exponent = 0;
                           break;
                        }

                        printf("    Usage %d, %s = %d %s", j, p->label, uref.value, 
                            get_units(unit, exponent));

                        switch (p->type) {
                        case T_INDEX:
                            printf(" %s\n", get_string(fd, uref.value));
                            break;
                        case T_BITS:  /* binary bits */
                            printf(" 0x%x\n", uref.value);
                            break;
                        case T_DATE:  /* packed integer date */
                            printf(" %4d-%02d-%02d\n", (uref.value >> 9) + 1980,
                                (uref.value >> 5) & 0xF, uref.value & 0x1F);
                            break;
                        case T_APCDATE: /* APC date */
                            v = uref.value;
                            yy = ((v>>4) & 0xF)*10 + (v&0xF) + 2000;
                            v >>= 8;
                            dd = ((v>>4) & 0xF)*10 + (v&0xF);
                            v >>= 8;
                            mm = ((v>>4) & 0xF)*10 + (v&0xF);	    
                            printf(" %4d-%02d-%02d\n", yy, mm, dd);
                        default:
                            printf("\n");
                            break;
                        }
                    }
                }
                rinfo.report_id |= HID_REPORT_ID_NEXT;
            }
        }
    }

    printf("\nWaiting for events ... (interrupt to exit)\n");
    fflush(stdout);

#endif

    start_seconds = time(NULL);
    FD_ZERO(&fdset);
    while (1) {
        if (fd < 0) {
            sleep(5);
            fd = open(evdev, O_RDONLY);
            if (fd < 0) {
                perror("\nOpen error");
                continue;
            }
            if (!find_application(fd, UPS_USAGE)) {
               fprintf(stderr, "\nCould not find_application.\n");
               close(fd);
               fd = -1;
            }
            continue;
        }
        switch (state) {
            case STATE_NORMAL:
                tv = NULL;
                break;
            case STATE_BATTERY:
            case STATE_DEBOUNCE:
                timev.tv_sec = DEBOUNCE_TIMEOUT;
                timev.tv_usec = 0;
                tv = &timev;
                break;
        }

        FD_SET(fd, &fdset);
        rd = select(fd+1, &fdset, NULL, NULL, tv);

        if (rd > 0) {
            rd = read(fd, ev, sizeof(ev));
            if (rd < (int) sizeof(ev[0])) {
                if (rd < 0)
                    perror("\nevtest: error reading");
                else
                    fprintf(stderr, "\nevtest: got short read from device!\n");
//		exit (1);
                close(fd);
                fd = -1;
                continue;
            } else {
            	printf("time %lu\n", time(NULL) - start_seconds);
            }

            for (i = 0; i < rd / sizeof(ev[0]); i++) {
#ifdef DEBUG
                    printf("Event: %s = %d\n",
                           info(ev[i].hid), ev[i].value);
#endif /* DEBUG */

                if (ev[i].hid == UPS_SHUTDOWN_IMMINENT && ev[i].value == 1) {
                    log_status("UPS shutdown imminent!");
                    powerfail(-1);
                    state = STATE_BATTERY;
                    RemainingCapacity = -1;
                }
                switch (state) {
                case STATE_BATTERY:
                    if (ev[i].hid == UPS_DISCHARGING && ev[i].value == 0) {
                        log_status("System back on AC power");
                        powerfail(0);
                        state = STATE_NORMAL;
                        RemainingCapacity = -1;
                    }
                    break;
                case STATE_DEBOUNCE:
                    if (ev[i].hid == UPS_DISCHARGING && ev[i].value == 0) {
                        state = STATE_NORMAL;
                        RemainingCapacity = -1;
                    }
                    break;
                case STATE_NORMAL:
                    if (ev[i].hid == UPS_DISCHARGING && ev[i].value == 1) {
                        state = STATE_DEBOUNCE;
                        RemainingCapacity = -1;
                    }
                    break;
                }
            }
        } else {
            /* Our timer has expired */
            switch (state) {
            case STATE_DEBOUNCE:
                log_status("System switched to battery power");
                state = STATE_BATTERY;
                powerfail(1);
                break;
            default:  
                break;
            }
        }
        fflush(stdout);
    }
}
