/* * $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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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; char *label; } ups_info[] = { /* MGE & APC */ { 0x000000, T_NONE, "---" }, /* Page 0x84 is the Power Device Page */ { 0x840000, T_NONE, "UPS-Power" }, { 0x840001, T_INDEX, "iName" }, { 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_NONE, "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_NONE, "APC_USB_FirmwareRevision" }, { 0xFF86007C, T_NONE, "APCForceShutdown" }, { 0xFF86007D, T_UNITS, "APCDelayBeforeShutdown" }, { 0xFF86007E, T_UNITS, "APCDelayBeforeStartup" }, }; #define UPS_INFO_SZ (sizeof(ups_info)/sizeof(ups_info[0])) char *reports[] = { "Unknown", "Input", "Output", "Feature" }; static int CapacityMode = 2; /* default = % */ char unknown[24]; #define MADDR "stewart@wetlogic.net" void log_status(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 inline char* info(unsigned int detail) { 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) { 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 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 char *get_units(unsigned unit, unsigned 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, i, j, RemainingCapacity; 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) { struct hiddev_usage_ref uref; /* 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, 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\n", i, info(finfo.application), info(finfo.physical)); memset(&uref, 0, sizeof(uref)); for (j = 0; j < finfo.maxusage; j++) { unsigned unit, 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", 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); } }