/* Copyright (C) 2003 Peter Selinger 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 */ /* a simple program to communicate with a Belkin UPS on a "console": the user types in the following commands: set get state quit help */ #include #include #include #include #include #include #include #include #include /* ---------------------------------------------------------------------- */ /* low-level i/o */ /* Open and prepare a serial port for communication with a Belkin Universal UPS. DEVICE is the name of the serial port. It will be opened in blocking read/write mode, and the appropriate communications parameters will be set. The device will also be sent a special signal (clear DTR, set RTS) to cause the UPS to switch from "dumb" to "smart" mode, and any pending data (=garbage) will be discarded. After this call, the device is ready for reading and writing via read(2) and write(2). Return a valid file descriptor on success, or else -1 with errno set. */ int belkin_open_tty(char *device) { int fd; struct termios tios; struct flock flock; char buf[128]; const int tiocm_dtr = TIOCM_DTR; const int tiocm_rts = TIOCM_RTS; int r; /* open the device */ fd = open(device, O_RDWR | O_NONBLOCK); if (fd == -1) { return -1; } /* set communications parameters: 2400 baud, 8 bits, 1 stop bit, no parity, enable reading, hang up when done, ignore modem control lines. */ memset(&tios, 0, sizeof(tios)); tios.c_cflag = B2400 | CS8 | CREAD | HUPCL | CLOCAL; tios.c_cc[VMIN] = 1; tios.c_cc[VTIME] = 0; r = tcsetattr(fd, TCSANOW, &tios); if (r == -1) { close(fd); return -1; } /* signal the UPS to enter "smart" mode. This is done by setting RTS and dropping DTR for at least 0.25 seconds. RTS and DTR refer to two specific pins in the 9-pin serial connector. Note: this must be done for at least 0.25 seconds for the UPS to react. Ignore any errors, as this probably means we are not on a "real" serial port. */ ioctl(fd, TIOCMBIC, &tiocm_dtr); ioctl(fd, TIOCMBIS, &tiocm_rts); /* flush both directions of serial port: throw away all data in transit */ r = tcflush(fd, TCIOFLUSH); if (r == -1) { close(fd); return -1; } /* lock the port */ memset(&flock, 0, sizeof(flock)); flock.l_type = F_RDLCK; r = fcntl(fd, F_SETLK, &flock); if (r == -1) { close(fd); return -1; } /* sleep at least 0.25 seconds for the UPS to wake up. Belkin's own software sleeps 1 second, so that's what we do, too. */ usleep(1000000); /* flush incoming data again, and read any remaining garbage bytes. There should not be any. */ r = tcflush(fd, TCIFLUSH); if (r == -1) { close(fd); return -1; } r = read(fd, buf, 127); if (r == -1 && errno != EAGAIN) { close(fd); return -1; } /* finally, switch to blocking i/o, so that future read/write calls will read or write at least one byte */ r = fcntl(fd, F_SETFL, 0); /* clear O_NONBLOCK */ if (r == -1 && errno != EAGAIN) { close(fd); return -1; } return fd; } /* blocking read */ int upsread(int fd, char *buf, int n) { int count = 0; int r; while (count < n) { r = read(fd, &buf[count], n-count); if (r == -1) { return -1; } count += r; } return count; } /* blocking write */ int upswrite(int fd, char *buf, int n) { int count = 0; int r; while (count < n) { r = write(fd, &buf[count], n-count); if (r == -1) { return -1; } count += r; } return count; } /* ---------------------------------------------------------------------- */ /* some private functions for talking to the UPS */ #define MAXMSGSIZE 25 /* calculate a Belkin checksum, i.e., add buf[0]...buf[n-1] */ static unsigned char belkin_checksum(unsigned char *buf, int n) { int i, res; res = 0; for (i=0; i bufsize) { return -1; } r = upsread(fd, &buf[0], 1); if (r==-1 || buf[0]!=0x7e) { fprintf(stderr, "Garbage read from UPS\n"); return -1; } n+=r; /* read instruction, size, and register */ if (n+3 > bufsize) { return -1; } r = upsread(fd, &buf[1], 3); if (r!=3) { fprintf(stderr, "Short read from UPS\n"); return -1; } n+=r; len = buf[2]; /* read data and checksum */ if (n+len > bufsize) { return -1; } r = upsread(fd, &buf[4], len); if (r!=len) { fprintf(stderr, "Short read from UPS\n"); return -1; } n+=r; /* check checksum */ if (belkin_checksum(buf, len+3) != buf[len+3]) { fprintf(stderr, "Bad checksum from UPS\n"); return -1; } return n; } /* read the value of a string register from UPS. Return NULL on failure, else an allocated string. */ static char *belkin_read_str(int fd, int reg) { unsigned char buf[MAXMSGSIZE]; int len, r; char *str; /* send the request */ buf[0] = 0x7e; buf[1] = 0x03; buf[2] = 0x02; buf[3] = reg; buf[4] = 0; buf[5] = belkin_checksum(buf, 5); r = upswrite(fd, buf, 6); if (r<0) { fprintf(stderr, "Failed write to UPS\n"); return NULL; } /* receive the answer */ r = belkin_receive(fd, buf, MAXMSGSIZE); if (r<0) { return NULL; } if ((buf[1]!=0x05 && buf[1]!=0x01) || buf[3] != reg) { fprintf(stderr, "Invalid response from UPS\n"); return NULL; } if (buf[1]==0x01) { return NULL; } /* convert the answer to a string */ len = buf[2]-1; str = (char *)malloc(len+1); if (!str) { fprintf(stderr, "malloc: %s\n", strerror(errno)); return NULL; } memcpy(str, &buf[4], len); str[len]=0; return str; } /* read the value of an integer register from UPS. Return -1 on failure. */ static int belkin_read_int(int fd, int reg) { unsigned char buf[MAXMSGSIZE]; int len, r; /* send the request */ buf[0] = 0x7e; buf[1] = 0x03; buf[2] = 0x02; buf[3] = reg; buf[4] = 0; buf[5] = belkin_checksum(buf, 5); r = upswrite(fd, buf, 6); if (r<0) { fprintf(stderr, "Failed write to UPS\n"); return -1; } /* receive the answer */ r = belkin_receive(fd, buf, MAXMSGSIZE); if (r<0) { return -1; } if ((buf[1]!=0x05 && buf[1]!=0x01) || buf[3] != reg) { fprintf(stderr, "Invalid response from UPS\n"); return -1; } if (buf[1]==0x01) { return -1; } /* convert the answer to an integer */ len = buf[2]-1; if (len==1) { return buf[4]; } else if (len==2) { return buf[4] + 256*buf[5]; } else { fprintf(stderr, "Invalid response from UPS\n"); return -1; } } /* write the value of an integer register to UPS. Return -1 on failure, else 0 */ static int belkin_write_int(int fd, int reg, int val) { unsigned char buf[MAXMSGSIZE]; int r; /* send the request */ buf[0] = 0x7e; buf[1] = 0x04; buf[2] = 0x03; buf[3] = reg; buf[4] = val & 0xff; buf[5] = (val>>8) & 0xff; buf[6] = belkin_checksum(buf, 6); r = upswrite(fd, buf, 7); if (r<0) { fprintf(stderr, "Failed write to UPS\n"); return -1; } /* receive the acknowledgement */ r = belkin_receive(fd, buf, MAXMSGSIZE); if (r<0) { return -1; } if ((buf[1]!=0x02 && buf[1]!=0x01) || buf[3] != reg) { fprintf(stderr, "Invalid response from UPS\n"); return -1; } if (buf[1]==0x01) { return -1; } return 0; } /* ---------------------------------------------------------------------- */ /* register descriptions */ struct register_s { int n; /* register number */ int str; /* is it a string? */ char *desc; /* one-line description */ }; struct register_s regs[] = { { 0x01, 0, "voltage rating" }, { 0x02, 0, "frequency rating" }, { 0x03, 0, "power rating" }, { 0x04, 0, "battery voltage rating" }, { 0x05, 0, "unknown" }, { 0x06, 0, "low transfer point" }, { 0x07, 0, "low transfer point upper bound" }, { 0x08, 0, "low transfer point lower bound" }, { 0x09, 0, "high transfer point" }, { 0x0a, 0, "high transfer point upper bound" }, { 0x0b, 0, "high transfer point lower bound" }, { 0x0c, 0, "voltage sensitivity" }, { 0x0d, 0, "UPS model" }, { 0x0e, 0, "UPS model" }, { 0x0f, 0, "firmware/ups type" }, { 0x10, 0, "test status" }, { 0x11, 0, "alarm status" }, { 0x12, 0, "unknown" }, { 0x13, 0, "unknown" }, { 0x14, 0, "unknown" }, { 0x15, 0, "shutdown timer" }, { 0x16, 0, "restart timer" }, { 0x17, 0, "unknown" }, { 0x18, 0, "input voltage" }, { 0x19, 0, "input frequency" }, { 0x1a, 0, "temperature" }, { 0x1b, 0, "output voltage" }, { 0x1c, 0, "output frequency" }, { 0x1d, 0, "unknown" }, { 0x1e, 0, "loading level" }, { 0x1f, 0, "battery status" }, { 0x20, 0, "battery voltage" }, { 0x21, 0, "battery level" }, { 0x22, 0, "UPS status" }, { 0x23, 0, "battery status" }, { 0x3f, 0, "time remaining" }, { -1, 0, NULL} }; /* ---------------------------------------------------------------------- */ /* the user-level console */ /* strip whitespace from both ends of a string, descructively */ char *strip(char *s) { char *p; if (!s) return NULL; while (isspace(*s)) { s++; } p = s+strlen(s); while (p>s && isspace(p[-1])) p--; *p = 0; return s; } /* read an allocated, stripped line from stream. Return NULL on eof or error. */ char *my_getline(FILE *f) { char *buf; char *res; int size; /* current allocated size of buf */ int l; size = 200; /* should be large enough to hold an average line from * the shared library without having to realloc() the * buffer */ buf = (char *)malloc(size); res = fgets(buf, size, f); if (!res) { /* end of file */ free(buf); return NULL; } while (!strchr(buf, '\n')) { size *= 2; buf = (char *)realloc(buf, size); l = strlen(buf); res = fgets(buf+l, size-l, f); if (!res) /* end of file */ break; } /* strip whitespace from both ends */ res = strdup(strip(buf)); free(buf); return res; } int setcmd(int fd, char *args) { int reg, val, r; char *endptr; reg = strtol(args, &endptr, 16); if (endptr==args) { return -1; } while (*endptr==' ' || *endptr=='\t') { endptr++; } args = endptr; val = strtol(args, &endptr, 0); if (endptr==args) { return -1; } r = belkin_write_int(fd, reg, val); if (r==-1) { printf("Error\n"); } else { printf("OK\n"); } return 0; } int getcmd(int fd, char *args) { int reg, val; char *str; char *endptr; reg = strtol(args, &endptr, 16); if (endptr==args) { return -1; } if (reg==0x0d || reg==0x0e) { str = belkin_read_str(fd, reg); printf("%02x: '%s'\n", reg, str); free(str); } else { val = belkin_read_int(fd, reg); if (val==-1) { printf("%02x: invalid\n", reg); } else { printf("%02x: 0x%02x = %d\n", reg, val, val); } } return 0; } int listcmd(int fd) { int reg, i; char *str; int val; for (i=0; regs[i].n!=-1; i++) { reg = regs[i].n; if (reg==0x0d || reg==0x0e) { str = belkin_read_str(fd, reg); printf("%02x: '%s' %s\n", reg, str, regs[i].desc); free(str); } else { val = belkin_read_int(fd, reg); if (val==-1) { printf("%02x: invalid %s\n", reg, regs[i].desc); } else { printf("%02x: 0x%04x = %5d %s\n", reg, val, val, regs[i].desc); } } } return 0; } int helpcmd() { printf("Available commands:\n"); printf("set - set register to value\n"); printf("get - get value from register\n"); printf("state - get all registers\n"); printf("help - show this help text\n"); printf("quit - exit this program\n"); return 0; } int main(int ac, char *av[]) { int fd; char *name = av[0]; char *dev; char *prompt = "ups> "; char *line; if (ac != 2) { fprintf(stderr, "Usage: %s device\n", name); exit(1); } dev = av[1]; fd = belkin_open_tty(dev); if (fd==-1) { fprintf(stderr, "%s: %s\n", name, strerror(errno)); exit(1); } /* command loop */ while (1) { printf("%s", prompt); line = my_getline(stdin); if (line==NULL) { printf("\n"); break; } if (strncasecmp(line, "set ", 4)==0) { setcmd(fd, line+4); } else if (strncasecmp(line, "get ", 4)==0) { getcmd(fd, line+4); } else if (strcasecmp(line, "state")==0) { listcmd(fd); } else if (strcasecmp(line, "help")==0) { helpcmd(fd); } else if (strcasecmp(line, "quit")==0) { break; } else { printf("Command not recognized.\n"); helpcmd(fd); } free(line); } return 0; }