/* mge-shut.c - monitor MGE UPS for NUT with SHUT protocol * * Copyright (C) 2002 - 2012 * Arnaud Quette * * Sponsored by MGE UPS SYSTEMS * * 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 * */ #include #include "config.h" #include "main.h" #include "serial.h" #include "timehead.h" #include "mge-shut.h" #include "hidparser.h" #include "hidtypes.h" #include "common.h" /* for upsdebugx() etc */ /* --------------------------------------------------------------- */ /* Define "technical" constants */ /* --------------------------------------------------------------- */ #define DRIVER_NAME "Eaton / SHUT driver" #define DRIVER_VERSION "0.70" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Arnaud Quette ", DRV_STABLE, { NULL } }; #define MAX_TRY 4 /* global variables */ int commstatus = 0; int lowbatt = -1; int ondelay = DEFAULT_ONDELAY; int offdelay = DEFAULT_OFFDELAY; int notification = DEFAULT_NOTIFICATION; #define SD_RETURN 0 #define SD_STAYOFF 1 int sdtype = SD_RETURN; #define BYTESWAP(in) (((in & 0xFF) << 8) + ((in & 0xFF00) >> 8)) /* realign packet data according to Endianess */ static void align_request(hid_packet_t *sd) { #if WORDS_BIGENDIAN /* Sparc/Mips/... are big endian, USB/SHUT little endian */ (*sd).wValue = BYTESWAP((*sd).wValue); (*sd).wIndex = BYTESWAP((*sd).wIndex); (*sd).wLength = BYTESWAP((*sd).wLength); #endif } /* --------------------------------------------------------------- */ /* Global structures */ /* --------------------------------------------------------------- */ hid_desc_data_t hid_descriptor; device_desc_data_t device_descriptor; static long hValue; static HIDDesc_t *pDesc = NULL; /* parsed Report Descriptor */ u_char raw_buf[4096]; /* --------------------------------------------------------------- */ /* Function prototypes */ /* --------------------------------------------------------------- */ float expo(int a, int b); extern long FormatValue(long Value, u_char Size); static const char *hu_find_infoval(info_lkp_t *hid2info, long value); /* --------------------------------------------------------------- */ /* UPS Driver Functions */ /* --------------------------------------------------------------- */ void upsdrv_initinfo (void) { mge_info_item_t *item; upsdebugx(2, "entering initinfo()\n"); /* Get complete Model information */ shut_identify_ups (); printf("Detected %s [%s] on %s\n", dstate_getinfo("ups.model"), dstate_getinfo("ups.serial"), device_path); /* Device capabilities enumeration ----------------------------- */ for ( item = mge_info ; item->type != NULL ; item++ ) { /* Check if we are asked to stop (reactivity++) */ if (exit_flag != 0) return; /* avoid redundancy when multiple defines (RO/RW) */ if (dstate_getinfo(item->type) != NULL) continue; /* Special case for handling server side variables */ if (item->shut_flags & SHUT_FLAG_ABSENT) { /* Check if exists (if necessary) before creation */ if (item->item_path != NULL) { if (hid_get_value(item->item_path) != 1 ) continue; } else { /* Simply set the default value */ dstate_setinfo(item->type, "%s", item->dfl); dstate_setflags(item->type, item->flags); continue; } dstate_setinfo(item->type, "%s", item->dfl); dstate_setflags(item->type, item->flags); /* Set max length for strings, if needed */ if (item->flags & ST_FLAG_STRING) dstate_setaux(item->type, item->length); /* disable reading now item->shut_flags &= ~SHUT_FLAG_OK;*/ } else { if (hid_get_value(item->item_path) != 0 ) { item->shut_flags &= SHUT_FLAG_OK; dstate_setinfo(item->type, item->fmt, hValue); dstate_setflags(item->type, item->flags); /* Set max length for strings */ if (item->flags & ST_FLAG_STRING) dstate_setaux(item->type, item->length); } else { item->shut_flags &= ~SHUT_FLAG_OK; } } } /* commands ----------------------------------------------- */ dstate_addcmd("load.off"); dstate_addcmd("load.on"); dstate_addcmd("shutdown.return"); dstate_addcmd("shutdown.stayoff"); dstate_addcmd("test.battery.start"); dstate_addcmd("test.battery.stop"); /* install handlers */ upsh.setvar = hid_set_value; /* setvar; */ upsh.instcmd = instcmd; /* check if low battery level has been given to set it */ if (lowbatt != -1) { hid_set_value("battery.charge.low", getval ("lowbatt")); } } /* --------------------------------------------------------------- */ void upsdrv_updateinfo (void) { mge_info_item_t *item; const char *nutvalue; upsdebugx(2, "entering upsdrv_updateinfo()"); if (commstatus == 0) { if (shut_ups_start () != 0) { upsdebugx(2, "No communication with UPS, retrying"); dstate_datastale(); return; } else { upsdebugx(2, "Communication with UPS established"); } } shut_ups_status(); /* Device data walk ----------------------------- */ for ( item = mge_info ; item->type != NULL; item++ ) { /* Check if we are asked to stop (reactivity++) */ if (exit_flag != 0) return; if (item->shut_flags & SHUT_FLAG_ABSENT) continue; if (item->shut_flags & SHUT_FLAG_OK) { if(hid_get_value(item->item_path) != 0 ) { upsdebugx(3, "%s: hValue = %ld", item->item_path, hValue); /* upsdebugx(3, "%s: hValue = %ld (%ld)", item->item_path, hValue, hData.LogMax); */ /* need lookup'ed translation */ if (item->hid2info != NULL) { nutvalue = hu_find_infoval(item->hid2info, (long)hValue); if (nutvalue != NULL) dstate_setinfo(item->type, "%s", nutvalue); else dstate_setinfo(item->type, item->fmt, hValue); } else dstate_setinfo(item->type, item->fmt, hValue); dstate_dataok(); } else { if (shut_ups_start () != 0) dstate_datastale(); } } } } /* --------------------------------------------------------------- */ void upsdrv_shutdown (void) { char val[5]; if (sdtype == SD_RETURN) { /* set DelayBeforeStartup */ snprintf(val, sizeof(val), "%d", ondelay); hid_set_value("ups.timer.start", val); } /* set DelayBeforeShutdown */ snprintf(val, sizeof(val), "%d", offdelay); hid_set_value("ups.timer.shutdown", val); } /* --------------------------------------------------------------- */ void upsdrv_help (void) { upsdebugx(2, "entering upsdrv_help"); } /* --------------------------------------------------------------- */ /* list flags and values that you want to receive via -x */ void upsdrv_makevartable (void) { char msg[MAX_STRING]; upsdebugx (2, "entering upsdrv_makevartable()"); snprintf(msg, sizeof(msg), "Set low battery level, in %% (default=%d).", DEFAULT_LOWBATT); addvar (VAR_VALUE, "lowbatt", msg); snprintf(msg, sizeof(msg), "Set shutdown delay, in seconds (default=%d).", DEFAULT_OFFDELAY); addvar (VAR_VALUE, "offdelay", msg); snprintf(msg, sizeof(msg), "Set startup delay, in ten seconds units (default=%d).", DEFAULT_ONDELAY); addvar (VAR_VALUE, "ondelay", msg); snprintf(msg, sizeof(msg), "Set notification type, 1 = no, 2 = light, 3 = yes (default=%d).", DEFAULT_NOTIFICATION); addvar (VAR_VALUE, "notification", msg); } /* --------------------------------------------------------------- */ void upsdrv_initups (void) { upsdebugx(2, "entering upsdrv_initups()"); /* initialize serial port */ upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B2400); setline (1); /* get battery lowlevel */ if (getval ("lowbatt")) lowbatt = atoi (getval ("lowbatt")); /* on delay */ if (getval ("ondelay")) ondelay = atoi (getval ("ondelay")); /* shutdown delay */ if (getval ("offdelay")) offdelay = atoi (getval ("offdelay")); /* notification type */ if (getval ("notification")) notification = atoi (getval ("notification")); /* initialise communication */ if (shut_ups_start () != 0) fatalx(EXIT_FAILURE, "No communication with UPS"); else upsdebugx(2, "Communication with UPS established"); /* initialise HID communication */ if(hid_init_device() != 0) fatalx(EXIT_FAILURE, "Can't initialise HID device"); } /* --------------------------------------------------------------- */ void upsdrv_cleanup(void) { ser_close(upsfd, device_path); } /* --------------------------------------------------------------- */ int instcmd(const char *cmdname, const char *extra) { /* Shutdown UPS and return when power is restored */ if (!strcasecmp(cmdname, "shutdown.return")) { sdtype = SD_RETURN; upsdrv_shutdown(); return STAT_INSTCMD_HANDLED; } /* Shutdown UPS and stay off when power is restored */ if (!strcasecmp(cmdname, "shutdown.stayoff")) { sdtype = SD_STAYOFF; upsdrv_shutdown(); return STAT_INSTCMD_HANDLED; } /* Power off the load immediatly */ if (!strcasecmp(cmdname, "load.off")) { /* set DelayBeforeShutdown to 0 */ hid_set_value("ups.timer.shutdown", "0"); return STAT_INSTCMD_HANDLED; } /* Power on the load immediatly */ if (!strcasecmp(cmdname, "load.on")) { /* set DelayBeforeStartup to 0 */ hid_set_value("ups.timer.start", "0"); return STAT_INSTCMD_HANDLED; } /* Start battery test */ if (!strcasecmp(cmdname, "test.battery.start")) { /* set Test to 1 (Quick test) */ hid_set_value("ups.test.result", "1"); return STAT_INSTCMD_HANDLED; } /* Stop battery test */ if (!strcasecmp(cmdname, "test.battery.stop")) { /* set Test to 3 (Abort test) */ hid_set_value("ups.test.result", "3"); return STAT_INSTCMD_HANDLED; } upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname); return STAT_INSTCMD_UNKNOWN; } /***************************************************************************** * shut_ups_start () * * initiate communication with the UPS * * return 0 on success, -1 on failure * *****************************************************************************/ int shut_ups_start () { u_char c = SHUT_SYNC, r[1]; int try; upsdebugx (2, "entering shut_ups_start()\n"); r[0] = '\0'; switch (notification) { case OFF_NOTIFICATION: c = SHUT_SYNC_OFF; break; case LIGHT_NOTIFICATION: c = SHUT_SYNC_LIGHT; break; default: case COMPLETE_NOTIFICATION: c = SHUT_SYNC; break; } /* Sync with the UPS using Complete, Off or light notification */ for (try = 0; try < MAX_TRY; try++) { if ((shut_token_send(c)) == -1) { upsdebugx (3, "Communication error while writing to port"); return -1; } serial_read (1000, &r[0]); if (r[0] == c) { commstatus = 1; upsdebugx (3, "Syncing and notification setting done"); return 0; } } commstatus = 0; return -1; } /********************************************************************** * shut_identify_ups () * * Get SHUT device complete name * * return 0 on success, -1 on failure * *********************************************************************/ int shut_identify_ups () { char string[MAX_STRING]; char model[MAX_STRING]; const char *finalname = NULL; int retcode, tries=MAX_TRY; if (commstatus == 0) return -1; upsdebugx (2, "entering shut_identify_ups(0x%04x, 0x%04x)\n", device_descriptor.dev_desc.iManufacturer, device_descriptor.dev_desc.iProduct); /* Get strings iModel and iProduct */ while (tries > 0) { if (shut_get_string(device_descriptor.dev_desc.iProduct, string, 0x25) > 0) { strcpy(model, string); if(hid_get_value("UPS.PowerSummary.iModel") != 0 ) { if((shut_get_string(hValue, string, 0x25)) > 0) { finalname = get_model_name(model, string); upsdebugx (2, "iModel = %s", string); tries = 0; } } else { /* Try with "UPS.Flow.[4].ConfigApparentPower" */ if(hid_get_value("UPS.Flow.[4].ConfigApparentPower") != 0 ) { snprintf(string, sizeof(string), "%i", (int)hValue); finalname = get_model_name(model, string); } else finalname = get_model_name(model, NULL); tries = 0; } dstate_setinfo("ups.model", "%s", finalname); } else tries--; } /* Get strings iSerialNumber */ if (((retcode = shut_get_string(device_descriptor.dev_desc.iSerialNumber, string, 0x25)) > 0) && strcmp(string, "") && string[0] != '\t') { dstate_setinfo("ups.serial", "%s", string); } else dstate_setinfo("ups.serial", "unknown"); /* all went fine */ return 1; } /********************************************************************** * shut_wait_ack() * * wait for an ACK packet * * returns 0 on success, -1 on error, -2 on NACK, -3 on NOTIFICATION * *********************************************************************/ int shut_wait_ack (void) { u_char c[1]; c[0] = '\0'; serial_read (DEFAULT_TIMEOUT, &c[0]); if (c[0] == SHUT_OK) { upsdebugx (2, "shut_wait_ack(): ACK received"); return 0; } else if (c[0] == SHUT_NOK) { upsdebugx (2, "shut_wait_ack(): NACK received"); return -2; } else if ((c[0] & 0x0f) == SHUT_TYPE_NOTIFY) { upsdebugx (2, "shut_wait_ack(): NOTIFY received"); return -3; } upsdebugx (2, "shut_wait_ack(): Nothing received"); return -1; } /********************************************************************** * char_read (char *bytes, int size, int read_timeout) * * reads size bytes from the serial port * * bytes - buffer to store the data * size - size of the data to get * read_timeout - serial timeout (in milliseconds) * * return -1 on error, -2 on timeout, nb_bytes_readen on success * *********************************************************************/ static int char_read (char *bytes, int size, int read_timeout) { struct timeval serial_timeout; fd_set readfs; int readen = 0; int rc = 0; FD_ZERO (&readfs); FD_SET (upsfd, &readfs); serial_timeout.tv_usec = (read_timeout % 1000) * 1000; serial_timeout.tv_sec = (read_timeout / 1000); rc = select (upsfd + 1, &readfs, NULL, NULL, &serial_timeout); if (0 == rc) return -2; /* timeout */ if (FD_ISSET (upsfd, &readfs)) { int now = read (upsfd, bytes, size - readen); if (now < 0) { return -1; } else { bytes += now; readen += now; } } else { return -1; } return readen; } /********************************************************************** * serial_read (int read_timeout) * * return data one byte at a time * * read_timeout - serial timeout (in milliseconds) * * returns 0 on success, -1 on error, -2 on timeout * **********************************************************************/ int serial_read (int read_timeout, u_char *readbuf) { static u_char cache[512]; static u_char *cachep = cache; static u_char *cachee = cache; int recv; *readbuf = '\0'; /* if still data in cache, get it */ if (cachep < cachee) { *readbuf = *cachep++; return 0; /* return (int) *cachep++; */ } recv = char_read ((char *)cache, 1, read_timeout); if ((recv == -1) || (recv == -2)) return recv; cachep = cache; cachee = cache + recv; cachep = cache; cachee = cache + recv; if (recv) { upsdebugx(5,"received: %02x", *cachep); *readbuf = *cachep++; return 0; } return -1; } /********************************************************************** * serial_send (char *buf, int len) * * write the content of buf to the serial port * * buf - data to send * len - lenght of data to send * * returns number of bytes written on success, -1 on error * **********************************************************************/ int serial_send (u_char *buf, int len) { tcflush (upsfd, TCIFLUSH); upsdebug_hex (3, "sent", (u_char *)buf, len); return write (upsfd, buf, len); } /* * Serial HID UPS Transfer (SHUT) functions *********************************************************************/ /* Get and parse UPS status */ void shut_ups_status(void) { int try = 0, retcode = 0; /* clear status buffer before begining */ status_init(); /* Ensure to have at least basic status */ while (try < MAX_TRY) { if((retcode = hid_get_value("UPS.PowerSummary.PresentStatus.ACPresent")) != 0 ) { try = MAX_TRY; if(hValue == 1){ status_set("OL"); } else { status_set("OB"); } } else { /* retry to get data */ try++; } } if(hid_get_value("UPS.PowerSummary.PresentStatus.Discharging") != 0 ) { if(hValue == 1) status_set("DISCHRG"); } if(hid_get_value("UPS.PowerSummary.PresentStatus.Charging") != 0 ) { if(hValue == 1) status_set("CHRG"); } if(hid_get_value("UPS.PowerSummary.PresentStatus.ShutdownImminent") != 0 ) { if(hValue == 1) status_set("LB"); } if(hid_get_value("UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit") != 0 ) { if(hValue == 1) status_set("LB"); } if(hid_get_value("UPS.PowerSummary.PresentStatus.Overload") != 0 ) { if(hValue == 1) status_set("OVER"); } if(hid_get_value("UPS.PowerSummary.PresentStatus.NeedReplacement") != 0 ) { if(hValue == 1) status_set("RB"); } if(hid_get_value("UPS.PowerSummary.PresentStatus.Good") != 0 ) { if(hValue == 0) status_set("OFF"); } /* FIXME: extend ups.status for BYPASS: */ /* Manual bypass */ if(hid_get_value("UPS.PowerConverter.Input.[4].PresentStatus.Used") != 0 ) { if(hValue == 1) status_set("BYPASS"); } /* Automatic bypass */ if(hid_get_value("UPS.PowerConverter.Input.[2].PresentStatus.Used") != 0 ) { if(hValue == 1) status_set("BYPASS"); } status_commit(); } /* Calculate the SHUT checksum for the packet "buf" */ u_char shut_checksum(const u_char *buf, int bufsize) { int i; u_char chk=0; for(i=0; i0 && Retry>0) { Size=(datalen>=8) ? 8 : datalen; /* Packets need only to be sent once * NACK handling should take care of the rest */ if (Retry == 1) { /* Forge SHUT Frame */ SHUTRequest.bType = SHUT_TYPE_REQUEST + token; SHUTRequest.bLength = (Size<<4) + Size; SHUTRequest.data = *hdata; /* memcpy(&SHUTRequest.data.raw_pkt, hdata->raw_pkt, Size); */ sdata.shut_pkt = SHUTRequest; sdata.raw_pkt[(Size+3) - 1] = shut_checksum(sdata.shut_pkt.data.raw_pkt, Size); upsdebugx (4, "shut_checksum = %2x", sdata.raw_pkt[(Size+3)-1]); serial_send (sdata.raw_pkt, Size+3); } i = shut_wait_ack (); if (i == 0) { datalen-=Size; Retry=5; upsdebugx (4, "received ACK"); break; } else if ((i == -1) || (i == -3)) { /* retry a finite number of times if something wrong happened while * sending like a notification or a NACK */ if (Retry >= MAX_TRY) { upsdebugx(2, "Max tries reached while waiting for ACK, still getting errors"); return i; } else { upsdebugx(4, "Retry = %i", Retry); /* Send a NACK to get a resend from the UPS */ shut_token_send(SHUT_NOK); Retry++; } } } return (datalen==0); } int shut_packet_recv (u_char *Buf, int datalen) { u_char Start[2]; u_char Frame[8]; u_char Chk[1]; u_short Size=8; u_short Pos=0; u_char Retry=0; int recv; upsdebugx (4, "entering shut_packet_recv (%i)", datalen); while(datalen>0 && Retry<3) { if(serial_read (DEFAULT_TIMEOUT, &Start[0]) >= 0) { if(Start[0]==SHUT_SYNC) { upsdebugx (4, "received SYNC token"); memcpy(Buf, Start, 1); return 1; } else { /* if(((Start[1] = serial_read (DEFAULT_TIMEOUT)) >= 0) && */ if((serial_read (DEFAULT_TIMEOUT, &Start[1]) >= 0) && ((Start[1]>>4)==(Start[1]&0x0F))) { upsdebug_hex(3, "Receive", Start, 2); Size=Start[1]&0x0F; for(recv=0;recv OK", Chk[0]); memcpy(Buf, Frame, Size); datalen-=Size; Buf+=Size; Pos+=Size; Retry=0; shut_token_send(SHUT_OK); /* Check if there are more data to receive */ if((Start[0] & 0xf0) == SHUT_PKT_LAST) { /* Check if it's a notification */ if ((Start[0] & 0x0f) == SHUT_TYPE_NOTIFY) { /* TODO: process notification (dropped for now) */ upsdebugx (4, "=> notification"); datalen+=Pos; Pos=0; } else return Pos; } else upsdebugx (4, "need more data (%i)!", datalen); } else { upsdebugx (4, "shut_checksum: %02x => NOK", Chk[0]); shut_token_send(SHUT_NOK); Retry++; } } else return 0; } } else Retry++; } /* while */ return 0; } /* * Human Interface Device (HID) functions *********************************************************************/ /********************************************************************** * shut_get_descriptor(int desctype, u_char *pkt) * * get descriptor specified by DescType and return it in Buf * * desctype - from shutdataType * pkt - where to store the report received * * return 0 on success, -1 on failure, -2 on NACK * *********************************************************************/ int shut_get_descriptor(int desctype, u_char *pkt, int reportlen) { hid_packet_t HIDRequest; hid_data_t data; int retcode; upsdebugx (2, "entering shut_get_descriptor(n %02x, %i)", desctype, reportlen); HIDRequest.bmRequestType = REQUEST_TYPE_USB+(desctype>=HID_DESCRIPTOR?1:0); HIDRequest.bRequest = 0x06; HIDRequest.wValue = (desctype<<8); HIDRequest.wIndex = 0x0000; HIDRequest.wLength = reportlen; align_request(&HIDRequest); data.hid_pkt = HIDRequest; /* if((retcode = shut_packet_send (&data, sizeof(data), SHUT_PKT_LAST)) > 0) */ if((retcode = shut_packet_send (&data, 8, SHUT_PKT_LAST)) > 0) { if((retcode = shut_packet_recv (pkt, reportlen)) > 0) { upsdebug_hex(3, "shut_get_descriptor", pkt, retcode); return retcode; } else return retcode; } return retcode; } /********************************************************************** * shut_get_string(int index, u_char *pkt, int reportlen) * * get descriptor specified by DescType and return it in Buf * * index - from shutdataType * string - where to store the string received * strlen - length of string * * return string size on success, -1 on failure, -2 on NACK * *********************************************************************/ int shut_get_string(int strindex, char *string, int stringlen) { hid_packet_t HIDRequest; hid_data_t data; int retcode; u_char buf[MAX_STRING]; upsdebugx (2, "entering shut_get_string(%02x)", strindex); HIDRequest.bmRequestType = REQUEST_TYPE_USB; HIDRequest.bRequest = 0x06; HIDRequest.wValue = strindex+(STRING_DESCRIPTOR<<8); HIDRequest.wIndex = 0x0000; HIDRequest.wLength = (stringlen<<8); /* (reportlen&0xFF)&(reportlen>>8); */ align_request(&HIDRequest); data.hid_pkt = HIDRequest; if((retcode = shut_packet_send (&data, 8, SHUT_PKT_LAST)) >0) { upsdebug_hex(3, "shut_get_string", data.raw_pkt, 8); if((retcode = shut_packet_recv (buf, stringlen)) > 0) { upsdebug_hex(3, "shut_get_string", buf, retcode); make_string(buf, retcode, string); upsdebugx(2, "string: %s", string); return strlen(string); } else return retcode; } return 0; } /********************************************************************** * shut_get_report(int id, u_char *pkt, int reportlen) * * get report specified by id and return it in pkt * * id - from shutdataType * pkt - where to store the string received * strlen - length of string * * return report size on success, -1 on failure, -2 on NACK * *********************************************************************/ int shut_get_report(int id, u_char *pkt, int reportlen) { hid_packet_t HIDRequest; hid_data_t data; int retcode; upsdebugx (2, "entering shut_get_report(id: %02x, len: %02x)", id, reportlen); HIDRequest.bmRequestType = REQUEST_TYPE_GET_REPORT; HIDRequest.bRequest = 0x01; HIDRequest.wValue = id+(HID_REPORT_TYPE_FEATURE<<8); HIDRequest.wIndex = 0x0000; HIDRequest.wLength = reportlen; align_request(&HIDRequest); data.hid_pkt = HIDRequest; /* if((retcode = shut_packet_send (&data, sizeof(data), SHUT_PKT_LAST)) > 0) */ if((retcode = shut_packet_send (&data, 8, SHUT_PKT_LAST)) > 0) { if((retcode = shut_packet_recv (pkt, reportlen)) > 0) { upsdebug_hex(3, "shut_get_report", pkt, retcode); return retcode; } else return retcode; } return retcode; } /********************************************************************** * shut_set_report(int id, u_char *pkt, int reportlen) * * set report specified by id using pkt as value * * id - from shutdataType * pkt - what to put in report * strlen - length of report * * return string size on success, -1 on failure, -2 on NACK * *********************************************************************/ int shut_set_report(int id, u_char *pkt, int reportlen) { hid_packet_t HIDRequest; hid_data_t data; int retcode; upsdebugx (2, "entering shut_set_report(id: %02x, len: %02x)", id, reportlen); HIDRequest.bmRequestType = REQUEST_TYPE_SET_REPORT; HIDRequest.bRequest = 0x09; HIDRequest.wValue = id+(HID_REPORT_TYPE_FEATURE<<8); HIDRequest.wIndex = 0x0000; HIDRequest.wLength = reportlen; align_request(&HIDRequest); data.hid_pkt = HIDRequest; /* first packet to instruct a set command */ if((retcode = shut_packet_send (&data, sizeof(data), 0x0)) > 0) { /* second packet to give the actual data */ memcpy(&data.raw_pkt, pkt, reportlen); upsdebug_hex(3, "Set2", pkt, reportlen); retcode = shut_packet_send (&data, reportlen, SHUT_PKT_LAST); } return retcode; } /********************************************************************** * hid_init_device() * * Get Device/HID/Report descriptors from device and initialise * HID Parser for further actions * * return 0 on success, -1 on failure * *********************************************************************/ int hid_init_device() { int retcode; /* Get HID descriptor */ if((retcode = shut_get_descriptor(HID_DESCRIPTOR, hid_descriptor.raw_desc, 0x09)) > 0) { upsdebug_hex(3, "shut_get_descriptor(hid)", hid_descriptor.raw_desc, retcode); /* WORKAROUND: need to be fixed */ hid_descriptor.hid_desc.wDescriptorLength = hid_descriptor.raw_desc[7] + (hid_descriptor.raw_desc[8]<<8); upsdebugx(3, "HID Descriptor: \nbLength: \t\t0x%02x\nbDescriptorType: \t0x%02x\n", hid_descriptor.hid_desc.bLength, hid_descriptor.hid_desc.bDescriptorType); upsdebugx(3, "bcdHID: \t\t0x%04x\nbCountryCode: \t\t0x%02x\nbNumDescriptors: \t0x%02x\n", hid_descriptor.hid_desc.bcdHID, hid_descriptor.hid_desc.bCountryCode, hid_descriptor.hid_desc.bNumDescriptors); upsdebugx(3, "bReportDescriptorType: \t0x%02x\nwDescriptorLength: \t0x%04x", hid_descriptor.hid_desc.bReportDescriptorType, hid_descriptor.hid_desc.wDescriptorLength); /* Get Device descriptor */ if((retcode = shut_get_descriptor(DEVICE_DESCRIPTOR, device_descriptor.raw_desc, 0x12)) > 0) { upsdebug_hex(3, "shut_get_descriptor(device)", device_descriptor.raw_desc, retcode); upsdebugx(2, "Device Descriptor: \nbLength: \t\t0x%02x\nbDescriptorType:\ \t0x%02x\nbcdUSB: \t\t0x%04x\nbDeviceClass: \t\t0x%02x\nbDeviceSubClass:\ \t0x%02x\nbDeviceProtocol: \t0x%02x\nbMaxPacketSize0:\ \t0x%02x\nidVendor: \t\t0x%04x\nidProduct: \t\t0x%04x\nbcdDevice:\ \t\t0x%04x\niManufacturer: \t\t0x%02x\niProduct:\ \t\t0x%02x\niSerialNumber: \t\t0x%02x\nbNumConfigurations: \t0x%02x\n", device_descriptor.dev_desc.bLength, device_descriptor.dev_desc.bDescriptorType, device_descriptor.dev_desc.bcdUSB, device_descriptor.dev_desc.bDeviceClass, device_descriptor.dev_desc.bDeviceSubClass, device_descriptor.dev_desc.bDeviceProtocol, device_descriptor.dev_desc.bMaxPacketSize0, device_descriptor.dev_desc.idVendor, device_descriptor.dev_desc.idProduct, device_descriptor.dev_desc.bcdDevice, device_descriptor.dev_desc.iManufacturer, device_descriptor.dev_desc.iProduct, device_descriptor.dev_desc.iSerialNumber, device_descriptor.dev_desc.bNumConfigurations); /* Get Report descriptor */ if((retcode = shut_get_descriptor(REPORT_DESCRIPTOR, raw_buf, hid_descriptor.hid_desc.wDescriptorLength)) > 0) { upsdebug_hex(3, "shut_get_descriptor(report)", raw_buf, retcode); /* Parse Report Descriptor */ Free_ReportDesc(pDesc); pDesc = Parse_ReportDesc(raw_buf, retcode); if (!pDesc) { fatalx(EXIT_FAILURE, "Failed to parse report descriptor: %s", strerror(errno)); } } else fatalx(EXIT_FAILURE, "Unable to get Report Descriptor"); } else fatalx(EXIT_FAILURE, "Unable to get Device Descriptor"); } else fatalx(EXIT_FAILURE, "Unable to get HID Descriptor"); return 0; } /* translate HID string path to numeric path and return path depth */ ushort lookup_path(const char *HIDpath, HIDData_t *data) { ushort i = 0, cond = 1; int cur_usage; char buf[MAX_STRING]; char *start, *end; strncpy(buf, HIDpath, strlen(HIDpath)); buf[strlen(HIDpath)] = '\0'; start = end = buf; upsdebugx(3, "entering lookup_path(%s)", buf); while (cond) { if ((end = strchr(start, '.')) == NULL) { cond = 0; } else *end = '\0'; upsdebugx(4, "parsing %s", start); /* lookup code */ if ((cur_usage = hid_lookup_usage(start)) == -1) { upsdebugx(4, "%s wasn't found", start); return 0; } else { data->Path.Node[i] = cur_usage; i++; } if(cond) start = end +1 ; } data->Path.Size = i; return i; } /* Lookup this usage name to find its code (page + index) */ int hid_lookup_usage(char *name) { int i; upsdebugx(4, "Looking up %s", name); if (name[0] == '[') /* manage indexed collection */ return (0x00FF0000 + atoi(&name[1])); else { for (i = 0; (usage_lkp[i].usage_code != 0x0); i++) { if (!strcmp(usage_lkp[i].usage_name, name)) { upsdebugx(4, "hid_lookup_usage: found %04x", usage_lkp[i].usage_code); return usage_lkp[i].usage_code; } } } return -1; } /* Get an item value from a HID path */ int hid_get_value(const char *item_path) { int i, retcode; HIDData_t hData; upsdebugx(3, "entering hid_get_value(%s)", item_path); /* Prepare path of HID object */ hData.Type = ITEM_FEATURE; hData.ReportID = 0; if((retcode = lookup_path(item_path, &hData)) > 0) { upsdebugx(3, "Path depth = %i\n", retcode); for (i = 0; i 0) { GetValue((const u_char *) raw_buf, &hData, &hValue); upsdebug_hex(3, "Object's report", raw_buf, 10); upsdebugx(3, "Value = %ld", hValue); return 1; } else shut_ups_start(); } else { upsdebugx(3, "Can't find object"); return 0; } } else { upsdebugx(3, "Can't lookup object's path"); return 0; } return 0; } /* * Internal functions ****************************************************************************/ /* * Filter and reformat HID strings (suppress space * between each letter) * Note: string format in HID String Descriptor is * Byte1: Size of descriptor(=>string) * Byte2: String descriptor type (always 0x03) * Byte3 to byteN: UNICODE string (in US: xx 00 for a letter) */ void make_string(u_char *buf, int datalen, char *string) { int i, /* Skip size and type */ j=0; upsdebugx(4, "String descriptor: size = 0x%02x, type = 0x%02x", buf[0], buf[1]); /* TODO: add clean support for UNICODE */ for(i=2;i0) return (float) a * expo(a,b-1); if (b<0) return (float)((float)(1/(float)a) * (float) expo(a,b+1)); /* not reached */ return -1; } /* Format model names */ const char *get_model_name(char *iProduct, char *iModel) { models_name_t *model = NULL; upsdebugx(2, "get_model_name(%s, %s)\n", iProduct, iModel); /* Search for formatting rules */ for ( model = models_names ; model->iProduct != NULL ; model++ ) { upsdebugx(2, "comparing with: %s", model->finalname); if ( (!strncmp(iProduct, model->iProduct, strlen(model->iProduct))) && (!strncmp(iModel, model->iModel, strlen(model->iModel))) ) { upsdebugx(2, "Found %s\n", model->finalname); break; } } /* FIXME: if we end up with model->iProduct == NULL * then process name in a generic way (not yet supported models!) */ return model->finalname; } /* set r/w INFO_ element to a value. */ int hid_set_value(const char *varname, const char *val) { int retcode, i, replen; mge_info_item_t *shut_info_p; HIDData_t hData; upsdebugx(2, "============== entering hid_set_value(%s, %s) ==============", varname, val); /* 1) retrieve and check netvar & item_path */ shut_info_p = shut_find_info(varname); if (shut_info_p == NULL || shut_info_p->type == NULL || !(shut_info_p->flags & SHUT_FLAG_OK)) { upsdebugx(2, "hid_ups_set: info element unavailable %s", varname); return STAT_SET_UNKNOWN; } /* Checking item writability and HID Path */ if (!shut_info_p->flags & ST_FLAG_RW) { upsdebugx(2, "hid_ups_set: not writable %s", varname); return STAT_SET_UNKNOWN; } /* handle server side variable */ if (shut_info_p->shut_flags & SHUT_FLAG_ABSENT) { upsdebugx(2, "hid_ups_set: setting server side variable %s", varname); dstate_setinfo(shut_info_p->type, "%s", val); return STAT_SET_HANDLED; } else { /* SHUT_FLAG_ABSENT is the only case of HID Path == NULL */ if (shut_info_p->item_path == NULL) { upsdebugx(2, "hid_ups_set: ID Path is NULL for %s", varname); return STAT_SET_UNKNOWN; } } /* Prepare path of HID object */ hData.Type = ITEM_FEATURE; hData.ReportID = 0; if((retcode = lookup_path(shut_info_p->item_path, &hData)) > 0) { upsdebugx(3, "Path depth = %i\n", retcode); for (i = 0; i disabled for now if (shut_get_report(hData.ReportID, raw_buf, MAX_REPORT_SIZE) > 0) { GetValue((const u_char *) raw_buf, &hData, &hValue); upsdebugx(3, "Value = %d", hValue); if (hValue != atol(val)) upsdebugx(3, "FAILED"); else upsdebugx(3, "SUCCEED"); } else upsdebugx(3, "FAILED"); */ return STAT_SET_HANDLED; } else upsdebugx(3, "Object is constant"); } else upsdebugx(3, "Can't find object"); } else upsdebugx(3, "Can't lookup object's path"); return STAT_SET_UNKNOWN; } /* find info element definition in my info array. */ mge_info_item_t *shut_find_info(const char *varname) { mge_info_item_t *shut_info_p; for (shut_info_p = &mge_info[0]; shut_info_p->type != NULL; shut_info_p++) if (!strcasecmp(shut_info_p->type, varname)) return shut_info_p; fatalx(EXIT_FAILURE, "shut_find_info: unknown info type: %s", varname); return NULL; } /* find the NUT value matching that HID Item value */ static const char *hu_find_infoval(info_lkp_t *hid2info, long value) { info_lkp_t *info_lkp; upsdebugx(3, "hu_find_infoval: searching for value = %ld\n", value); for (info_lkp = hid2info; (info_lkp != NULL) && (strcmp(info_lkp->nut_value, "NULL")); info_lkp++) { if (info_lkp->hid_value == value) { upsdebugx(3, "hu_find_infoval: found %s (value: %ld)\n", info_lkp->nut_value, value); return info_lkp->nut_value; } } upsdebugx(3, "hu_find_infoval: no matching INFO_* value for this HID value (%ld)\n", value); return NULL; }