/* genericups.c - support for generic contact-closure UPS models Copyright (C) 1999 Russell Kroll 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 "config.h" /* must be first */ #include #include "main.h" #include "serial.h" #include "genericups.h" #include "nut_stdint.h" #define DRIVER_NAME "Generic contact-closure UPS driver" #define DRIVER_VERSION "1.38" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Russell Kroll ", DRV_STABLE, { NULL } }; static int upstype = -1; static void parse_output_signals(const char *value, int *line) { int old_line = *line; /* parse signals the serial port can output */ *line = 0; upsdebugx(4, "%s: enter", __func__); /* Note: for future drivers, please use strtok() or similar tokenizing * methods, such that it is easier to spot configuration mistakes. With * this code, a misspelled control line may go unnoticed. I'd fix it * The Right Way (tm), but these UPSes are ancient. */ if (strstr(value, "DTR") && !strstr(value, "-DTR")) { upsdebugx(3, "%s: override DTR", __func__); *line |= TIOCM_DTR; } if (strstr(value, "RTS") && !strstr(value, "-RTS")) { upsdebugx(3, "%s: override RTS", __func__); *line |= TIOCM_RTS; } if (strstr(value, "ST")) { upsdebugx(3, "%s: override ST", __func__); *line |= TIOCM_ST; } if (strstr(value, "CTS")) { fatalx(EXIT_FAILURE, "Can't override output with CTS (not an output)"); } if (strstr(value, "DCD")) { fatalx(EXIT_FAILURE, "Can't override output with DCD (not an output)"); } if (strstr(value, "RNG")) { fatalx(EXIT_FAILURE, "Can't override output with RNG (not an output)"); } if (strstr(value, "DSR")) { fatalx(EXIT_FAILURE, "Can't override output with DSR (not an output)"); } if (strstr(value, "NULL") || strstr(value, "none")) { upsdebugx(3, "%s: disable", __func__); *line = 0; } if(*line == old_line) { upslogx(LOG_NOTICE, "%s: output overrides specified, but no effective difference - check for typos?", __func__); } upsdebugx(4, "%s: exit", __func__); } static void parse_input_signals(const char *value, int *line, int *val) { /* parse signals the serial port can input */ int old_line = *line, old_val = *val; *line = 0; *val = 0; upsdebugx(4, "%s: enter", __func__); if (strstr(value, "CTS")) { *line |= TIOCM_CTS; if (!strstr(value, "-CTS")) { upsdebugx(3, "%s: override CTS (active low)", __func__); *val |= TIOCM_CTS; } else { upsdebugx(3, "%s: override CTS", __func__); } } if (strstr(value, "DCD")) { *line |= TIOCM_CD; if (!strstr(value, "-DCD")) { upsdebugx(3, "%s: override DCD (active low)", __func__); *val |= TIOCM_CD; } else { upsdebugx(3, "%s: override DCD", __func__); } } if (strstr(value, "RNG")) { *line |= TIOCM_RNG; if (!strstr(value, "-RNG")) { upsdebugx(3, "%s: override RNG (active low)", __func__); *val |= TIOCM_RNG; } else { upsdebugx(3, "%s: override RNG", __func__); } } if (strstr(value, "DSR")) { *line |= TIOCM_DSR; if (!strstr(value, "-DSR")) { upsdebugx(3, "%s: override DSR (active low)", __func__); *val |= TIOCM_DSR; } else { upsdebugx(3, "%s: override DSR", __func__); } } if (strstr(value, "DTR")) { fatalx(EXIT_FAILURE, "Can't override input with DTR (not an input)"); } if (strstr(value, "RTS")) { fatalx(EXIT_FAILURE, "Can't override input with RTS (not an input)"); } if (strstr(value, "ST")) { fatalx(EXIT_FAILURE, "Can't override input with ST (not an input)"); } if (strstr(value, "NULL") || strstr(value, "none")) { *line = 0; *val = 0; upsdebugx(3, "%s: disable", __func__); } if((*line == old_line) && (*val == old_val)) { upslogx(LOG_NOTICE, "%s: input overrides specified, but no effective difference - check for typos?", __func__); } upsdebugx(4, "%s: exit", __func__); } void upsdrv_initinfo(void) { char *v; /* setup the basics */ dstate_setinfo("ups.mfr", "%s", ((v = getval("mfr")) != NULL) ? v : upstab[upstype].mfr); dstate_setinfo("ups.model", "%s", ((v = getval("model")) != NULL) ? v : upstab[upstype].model); if ((v = getval("serial")) != NULL) { dstate_setinfo("ups.serial", "%s", v); } /* User wants to override the input signal definitions. See also upsdrv_initups(). */ if ((v = getval("OL")) != NULL) { parse_input_signals(v, &upstab[upstype].line_ol, &upstab[upstype].val_ol); upsdebugx(2, "parse_input_signals: OL overridden with %s\n", v); } if ((v = getval("LB")) != NULL) { parse_input_signals(v, &upstab[upstype].line_bl, &upstab[upstype].val_bl); upsdebugx(2, "parse_input_signals: LB overridden with %s\n", v); } if ((v = getval("RB")) != NULL) { parse_input_signals(v, &upstab[upstype].line_rb, &upstab[upstype].val_rb); upsdebugx(2, "parse_input_signals: RB overridden with %s\n", v); } if ((v = getval("BYPASS")) != NULL) { parse_input_signals(v, &upstab[upstype].line_bypass, &upstab[upstype].val_bypass); upsdebugx(2, "parse_input_signals: BYPASS overridden with %s\n", v); } } /* normal idle loop - keep up with the current state of the UPS */ void upsdrv_updateinfo(void) { int flags, ol, bl, rb, bypass, ret; ret = ioctl(upsfd, TIOCMGET, &flags); if (ret != 0) { upslog_with_errno(LOG_INFO, "ioctl failed"); ser_comm_fail("Status read failed"); dstate_datastale(); return; } /* Always online when OL is disabled */ ol = ((flags & upstab[upstype].line_ol) == upstab[upstype].val_ol); /* Always have the flags cleared when other status flags are disabled */ bl = upstab[upstype].line_bl != 0 && ((flags & upstab[upstype].line_bl) == upstab[upstype].val_bl); rb = upstab[upstype].line_rb != 0 && ((flags & upstab[upstype].line_rb) == upstab[upstype].val_rb); bypass = upstab[upstype].line_bypass != 0 && ((flags & upstab[upstype].line_bypass) == upstab[upstype].val_bypass); status_init(); if (bl) { status_set("LB"); /* low battery */ } if (ol) { status_set("OL"); /* on line */ } else { status_set("OB"); /* on battery */ } if (rb) { status_set("RB"); /* replace battery */ } if (bypass) { status_set("BYPASS"); /* battery bypass */ } status_commit(); upsdebugx(5, "ups.status: %s %s %s %s\n", ol ? "OL" : "OB", bl ? "BL" : "", rb ? "RB" : "", bypass ? "BYPASS" : ""); ser_comm_good(); dstate_dataok(); } /* show all possible UPS types */ static void listtypes(void) { int i; printf("Valid UPS types:\n\n"); for (i = 0; upstab[i].mfr != NULL; i++) { printf("%i: %s\n", i, upstab[i].desc); } } /* set the flags for this UPS type */ static void set_ups_type(void) { int i; if (!getval("upstype")) { fatalx(EXIT_FAILURE, "No upstype set - see help text / man page!"); } upstype = atoi(getval("upstype")); for (i = 0; upstab[i].mfr != NULL; i++) { if (upstype == i) { upslogx(LOG_INFO, "UPS type: %s\n", upstab[i].desc); return; } } listtypes(); fatalx(EXIT_FAILURE, "\nFatal error: unknown UPS type number"); } /* power down the attached load immediately */ void upsdrv_shutdown(void) { int flags, ret; if (upstype == -1) { fatalx(EXIT_FAILURE, "No upstype set - see help text / man page!"); } flags = upstab[upstype].line_sd; if (flags == -1) { fatalx(EXIT_FAILURE, "No shutdown command defined for this model!"); } if (flags == TIOCM_ST) { #ifndef HAVE_TCSENDBREAK fatalx(EXIT_FAILURE, "Need to send a BREAK, but don't have tcsendbreak!"); #endif ret = tcsendbreak(upsfd, 4901); if (ret != 0) { fatal_with_errno(EXIT_FAILURE, "tcsendbreak"); } return; } ret = ioctl(upsfd, TIOCMSET, &flags); if (ret != 0) { fatal_with_errno(EXIT_FAILURE, "ioctl TIOCMSET"); } if (getval("sdtime")) { long sdtime; sdtime = strtol(getval("sdtime"), (char **) NULL, 10); upslogx(LOG_INFO, "Holding shutdown signal for %ld seconds...\n", sdtime); if (sdtime > 0) { #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE #pragma GCC diagnostic ignored "-Wunreachable-code" #endif #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif /* Different platforms, different sizes, none fits all... */ if (sizeof(long) > sizeof(unsigned int) && sdtime < (long)UINT_MAX) { #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE #pragma GCC diagnostic pop #endif sleep((unsigned int)sdtime); } else { sleep(UINT_MAX); } } } } void upsdrv_help(void) { listtypes(); } void upsdrv_makevartable(void) { addvar(VAR_VALUE, "upstype", "Set UPS type (required)"); addvar(VAR_VALUE, "mfr", "Override manufacturer name"); addvar(VAR_VALUE, "model", "Override model name"); addvar(VAR_VALUE, "serial", "Specify the serial number"); addvar(VAR_VALUE, "CP", "Override cable power setting"); addvar(VAR_VALUE, "OL", "Override on line signal"); addvar(VAR_VALUE, "LB", "Override low battery signal"); addvar(VAR_VALUE, "RB", "Override replace battery signal"); addvar(VAR_VALUE, "BYPASS", "Override battery bypass signal"); addvar(VAR_VALUE, "SD", "Override shutdown setting"); addvar(VAR_VALUE, "sdtime", "Hold time for shutdown value (seconds)"); } void upsdrv_initups(void) { struct termios tio; char *v; set_ups_type(); upsfd = ser_open(device_path); if (tcgetattr(upsfd, &tio)) { fatal_with_errno(EXIT_FAILURE, "tcgetattr"); } /* don't hang up on last close */ tio.c_cflag &= ~((tcflag_t)HUPCL); if (tcsetattr(upsfd, TCSANOW, &tio)) { fatal_with_errno(EXIT_FAILURE, "tcsetattr"); } /* See if the user wants to override the output signal definitions this must be done here, since we might go to upsdrv_shutdown() immediately. Input signal definition override is handled in upsdrv_initinfo() */ if ((v = getval("CP")) != NULL) { parse_output_signals(v, &upstab[upstype].line_norm); upsdebugx(2, "parse_output_signals: CP overridden with %s\n", v); } if ((v = getval("SD")) != NULL) { parse_output_signals(v, &upstab[upstype].line_sd); upsdebugx(2, "parse_output_signals: SD overridden with %s\n", v); } if (ioctl(upsfd, TIOCMSET, &upstab[upstype].line_norm)) { fatal_with_errno(EXIT_FAILURE, "ioctl TIOCMSET"); } } void upsdrv_cleanup(void) { ser_close(upsfd, device_path); }