/* * $Id: evtest.c,v 1.10 2000/08/17 18:56:37 vojtech Exp $ * * Copyright (c) 1999-2000 Vojtech Pavlik * * Event 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 , or by paper mail: * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic */ #include #include #include #include #include #include #include #include #include #include #include #define PWRSTAT "/etc/powerstatus" #define LOGFILE "/var/log/usbupsd.log" #define PIDFILE "/var/run/usbupsd.pid" #define DEBOUNCE_TIMEOUT 60 #define UPS_USAGE 0x840004 #define UPS_SERIAL 0x8400ff #define BAT_CHEMISTRY 0x850089 #define UPS_SHUTDOWN_IMMINENT 0x840069 #define UPS_BATTERY_VOLTAGE 0x840030 #define UPS_BELOW_RCL 0x850042 #define UPS_CHARGING 0x850044 #define UPS_DISCHARGING 0x850045 #define UPS_REMAINING_CAPACITY 0x850066 #define UPS_RUNTIME_TO_EMPTY 0x850068 #define UPS_AC_PRESENT 0x8500d0 #define STATE_NORMAL 0 #define STATE_DEBOUNCE 1 #define STATE_BATTERY 2 static FILE *log_output; #define DEBUG #ifdef DEBUG struct { unsigned usage; char *label; } ups_info[] = { /* UPS System Page */ { UPS_SHUTDOWN_IMMINENT, "ShutdownImminent (bool)" }, { UPS_BATTERY_VOLTAGE, "Battery Voltage (centivolts)" }, /* Battery System Page */ { UPS_BELOW_RCL, "BelowRemainingCapacityLimit (bool)" }, { UPS_CHARGING, "Charging (bool)" }, { UPS_DISCHARGING, "Discharging (bool)" }, { UPS_REMAINING_CAPACITY, "RemainingCapacity (percent)" }, { UPS_RUNTIME_TO_EMPTY, "RunTimeToEmpty (seconds)" }, { UPS_AC_PRESENT, "ACPresent (bool)" }, }; #define UPS_INFO_SZ (sizeof(ups_info)/sizeof(ups_info[0])) void log_status(char *msg) { char buf[256]; fprintf(log_output, "[Log message \"%s\"]\n", msg); system(buf); } static inline int info_idx(unsigned int detail) { int i; for (i = 0; i < UPS_INFO_SZ; i++) { if (ups_info[i].usage == detail) { return i; } } return -1; } #else /* DEBUG */ #define log_status(s) #endif /* DEBUG */ /* Tell init the power has either gone or is back. */ void powerfail(int state) { 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); } static inline int find_application(int fd, unsigned usage) { int i = 0, ret; while ((ret = ioctl(fd, HIDIOCAPPLICATION, i)) > 0 && ret != usage) i++; return (ret == usage); } void hupSignal(int sig) { time_t ttime = time(NULL); if (log_output == stdout) return; fclose(log_output); log_output = fopen(LOGFILE, "a"); if (log_output == NULL) exit(1); fprintf(log_output, "Log restarted %s\n", ctime(&ttime)); } int main (int argc, char **argv) { int fd = -1, rd, i; struct hiddev_event ev[64]; struct hiddev_devinfo dinfo; char name[256] = "Unknown"; int state = 0; int run_as_daemon = 0; fd_set fdset; struct timeval timev, *tv = NULL; struct hiddev_usage_ref uref; struct hiddev_string_descriptor sdesc; time_t curr_time; if (argc > 1 && !strcmp(argv[1], "-daemon")) { run_as_daemon = 1; argc--; argv++; } if (argc < 2) { char evdev[20]; for (i = 0; i < 4; i++) { sprintf(evdev, "/dev/usb/hid/hiddev%d", i); if ((fd = open(evdev, O_RDONLY)) >= 0) { if (find_application(fd, UPS_USAGE)) break; close(fd); } } if (i >= 4) { fprintf(stderr, "Couldn't find UPS device.\n"); exit(1); } } else { if ((fd = open(argv[argc - 1], 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 { int 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, dinfo.version); printf("applications [%d]: %04x", dinfo.num_applications, ioctl(fd, HIDIOCAPPLICATION, 0)); for (i = 1; i < dinfo.num_applications; i++) printf(", %04x", ioctl(fd, HIDIOCAPPLICATION, i)); printf("\n"); printf("HID: bus: %d devnum: %d ifnum: %d\n", dinfo.busnum, dinfo.devnum, dinfo.ifnum); } #endif ioctl(fd, HIDIOCGNAME(sizeof(name)), name); printf("UPS HID device name: \"%s\"\n", name); ioctl(fd, HIDIOCINITREPORT, 0); 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) { sdesc.index = uref.value; if (ioctl(fd, HIDIOCGSTRING, &sdesc) < 0) strcpy(sdesc.value, "Unknown"); printf("Battery Chemistry: \"%s\" (%d)\n", sdesc.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_SERIAL; if (ioctl(fd, HIDIOCGUSAGE, &uref) == 0) { sdesc.index = uref.value; if (ioctl(fd, HIDIOCGSTRING, &sdesc) < 0) strcpy(sdesc.value, "Unknown"); printf("Serial Number: \"%s\" (%d)\n", sdesc.value, uref.value); } if (run_as_daemon) { FILE *pidFile; struct sigaction hupAction; daemon(0, 0); log_output = fopen(LOGFILE, "a"); if (log_output == NULL) log_output = stdout; pidFile = fopen(PIDFILE, "w"); if (pidFile != NULL) { fprintf(pidFile, "%d\n", getpid()); fclose(pidFile); } memset(&hupAction, 0, sizeof(hupAction)); hupAction.sa_handler = hupSignal; sigaction(SIGHUP, &hupAction, NULL); } else { log_output = stdout; } #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; char *rtype_str; for (rtype = HID_REPORT_TYPE_MIN; rtype <= HID_REPORT_TYPE_MAX; rtype++) { switch (rtype) { case HID_REPORT_TYPE_INPUT: rtype_str = "Input"; break; case HID_REPORT_TYPE_OUTPUT: rtype_str = "Output"; break; case HID_REPORT_TYPE_FEATURE: rtype_str = "Feature"; break; default: rtype_str = "Unknown"; break; } fprintf(log_output, "Reports of type %s (%d):\n", rtype_str, rtype); rinfo.report_type = rtype; rinfo.report_id = HID_REPORT_ID_FIRST; while (ioctl(fd, HIDIOCGREPORTINFO, &rinfo) >= 0) { fprintf(log_output, " Report id: %d (%d fields)\n", rinfo.report_id, rinfo.num_fields); for (i = 0; i < rinfo.num_fields; i++) { 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); fprintf(log_output, " Field: %d: app: %04x phys %04x " "flags %x (%d usages) unit %x exp %d\n", i, finfo.application, finfo.physical, finfo.flags, finfo.maxusage, finfo.unit, finfo.unit_exponent); memset(&uref, 0, sizeof(uref)); for (j = 0; j < finfo.maxusage; j++) { 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); fprintf(log_output, " Usage: %04x val %d\n", uref.usage_code, uref.value); } } rinfo.report_id |= HID_REPORT_ID_NEXT; } } if (!run_as_daemon) fprintf(log_output, "Waiting for events ... (interrupt to exit)\n"); } fflush(log_output); #endif FD_ZERO(&fdset); while (1) { switch (state) { case STATE_NORMAL: case STATE_BATTERY: tv = NULL; break; case STATE_DEBOUNCE: 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(log_output, "\nevtest: got short read from device!\n"); if (run_as_daemon) unlink(PIDFILE); exit (1); } for (i = 0; i < rd / sizeof(ev[0]); i++) { #ifdef DEBUG { int idx = info_idx(ev[i].hid); curr_time = time(NULL); strftime(name, sizeof(name), "%b %d %T", localtime(&curr_time)); fprintf(log_output, "%s: Event: usage %x (%s), value %d\n", name, ev[i].hid, (idx >= 0) ? ups_info[idx].label : "Unknown", ev[i].value); } #endif /* DEBUG */ if (ev[i].hid == UPS_DISCHARGING) { if (ev[i].value == 1) { state = STATE_DEBOUNCE; timev.tv_sec = DEBOUNCE_TIMEOUT; timev.tv_usec = 0; } else { if (state == STATE_BATTERY) { log_status("System back on AC power"); powerfail(0); } state = STATE_NORMAL; } } else if (ev[i].hid == UPS_SHUTDOWN_IMMINENT && ev[i].value == 1) { log_status("UPS shutdown imminent!"); powerfail(-1); } } } else { if (state == STATE_DEBOUNCE) { log_status("System switched to battery power"); state = STATE_BATTERY; powerfail(1); } } fflush(log_output); } }