/* * safenet.c - model specific routines for following units: * * - Fairstone L525/-625/-750 * - Fenton P400/-600/-800 * - Gemini UPS625/-1000 * - Powerwell PM525A/-625A/-800A/-1000A/-1250A * - Repotec RPF525/-625/-800/-1000 * - Soltec Winmate 525/625/800/1000 * - Sweex 500/1000 * - others using SafeNet software and serial interface * * Status: * 20081102/Revision 1.41 - Arjen de Korte * - allow more time for reading reply to command * 20081106/Revision 1.5 - Arjen de Korte * - changed communication with UPS * - improved handling of battery & system test * 20081228/Revision 1.6 - Arjen de Korte * - add ondelay and offdelay * * Copyright (C) 2003-2008 Arjen de Korte * * 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 "main.h" #include "serial.h" #include "safenet.h" #define DRIVER_NAME "Generic SafeNet UPS driver" #define DRIVER_VERSION "1.6" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Arjen de Korte ", DRV_STABLE, { NULL } }; /* * Here we keep the last known status of the UPS */ static union { char reply[10]; struct safenet status; } ups; static int ondelay = 1; /* minutes */ static int offdelay = 30; /* seconds */ static int safenet_command(const char *command) { char reply[32]; int i, ret; /* * Get rid of whatever is in the in- and output buffers. */ ser_flush_io(upsfd); /* * Send the command and read back the status line. When we just send * a status polling command, it will return the actual status. */ ret = ser_send(upsfd, "%s", command); if (ret < 0) { upsdebug_with_errno(3, "send"); return -1; } if (ret == 0) { upsdebug_with_errno(3, "send: timeout"); return -1; } upsdebugx(3, "send: %.*s", (int)strcspn(command, "\r"), command); /* * Read the reply from the UPS. */ ret = ser_get_buf(upsfd, reply, sizeof(reply), 1, 0); if (ret < 0) { upsdebug_with_errno(3, "read"); return -1; } if (ret == 0) { upsdebugx(3, "read: timeout"); return -1; } upsdebugx(3, "read: %.*s", (int)strcspn(reply, "\r"), reply); /* * We check if the reply looks like a valid status. */ if ((ret != 12) || (reply[0] != '$') || (strspn(reply+1, "AB") != 10)) { return -1; } for (i = 0; i < 10; i++) { ups.reply[i] = ((reply[i+1] == 'B') ? 1 : 0); } return 0; } static void safenet_update(void) { status_init(); if (ups.status.onbattery) { status_set("OB"); } else { status_set("OL"); } if (ups.status.batterylow) { status_set("LB"); } if (ups.status.overload) { status_set("OVER"); } if (ups.status.batteryfail) { status_set("RB"); } if (ups.status.systemtest) { status_set("CAL"); } alarm_init(); if (ups.status.systemfail) { alarm_set("System selftest fail!"); } alarm_commit(); status_commit(); } static int instcmd(const char *cmdname, const char *extra) { if (!strcasecmp(cmdname, "beeper.off")) { /* compatibility mode for old command */ upslogx(LOG_WARNING, "The 'beeper.off' command has been renamed to 'beeper.mute' for this driver"); return instcmd("beeper.mute", NULL); } if (!strcasecmp(cmdname, "beeper.on")) { /* compatibility mode for old command */ upslogx(LOG_WARNING, "The 'beeper.on' command has been renamed to 'beeper.enable'"); return instcmd("beeper.enable", NULL); } /* * Start the UPS selftest */ if (!strcasecmp(cmdname, "test.battery.start")) { if (safenet_command(COM_BATT_TEST)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * Stop the UPS selftest */ if (!strcasecmp(cmdname, "test.battery.stop")) { if (safenet_command(COM_STOP_TEST)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * Start simulated mains failure */ if (!strcasecmp (cmdname, "test.failure.start")) { if (safenet_command(COM_MAINS_TEST)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * Stop simulated mains failure */ if (!strcasecmp (cmdname, "test.failure.stop")) { if (safenet_command(COM_STOP_TEST)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * If beeper is off, toggle beeper state (so it should be ON after this) */ if (!strcasecmp(cmdname, "beeper.enable")) { if (ups.status.silenced && safenet_command(COM_TOGGLE_BEEP)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * If beeper is not off, toggle beeper state (so it should be OFF after this) * Unfortunately, this only mutes the beeper, it turns back on for the next * event automatically (no way to stop this, besides side cutters) */ if (!strcasecmp(cmdname, "beeper.mute")) { if (!ups.status.silenced && safenet_command(COM_TOGGLE_BEEP)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * Toggle beeper state unconditionally */ if (!strcasecmp(cmdname, "beeper.toggle")) { if (safenet_command(COM_TOGGLE_BEEP)) { return STAT_INSTCMD_FAILED; } else { return STAT_INSTCMD_HANDLED; } } /* * Shutdown and wait for the power to return */ if (!strcasecmp(cmdname, "shutdown.return")) { char command[] = SHUTDOWN_RETURN; command[4] += ((offdelay % 1000) / 100); command[5] += ((offdelay % 100) / 10); command[6] += (offdelay % 10); safenet_command(command); return STAT_INSTCMD_HANDLED; } /* * Shutdown and reboot */ if (!strcasecmp(cmdname, "shutdown.reboot")) { char command[] = SHUTDOWN_REBOOT; command[3] += ((offdelay % 1000) / 100); command[4] += ((offdelay % 100) / 10); command[5] += (offdelay % 10); command[7] += ((ondelay % 10000) / 1000); command[8] += ((ondelay % 1000) / 100); command[9] += ((ondelay % 100) / 10); command[10] += (ondelay % 10); safenet_command(command); return STAT_INSTCMD_HANDLED; } upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname); return STAT_INSTCMD_UNKNOWN; } void upsdrv_initinfo(void) { int retry = 3; char *v; dstate_setinfo("driver.version.internal", "%s", DRIVER_VERSION); usleep(100000); /* * Very crude hardware detection. If an UPS is attached, it will set DSR * to 1. Bail out if it isn't. */ if (!ser_get_dsr(upsfd)) { fatalx(EXIT_FAILURE, "Serial cable problem or nothing attached to %s", device_path); } /* * Initialize the serial interface of the UPS by sending the magic * string. If it does not respond with a valid status reply, * display an error message and give up. */ while (safenet_command(COM_INITIALIZE)) { if (--retry) { continue; } fatalx(EXIT_FAILURE, "SafeNet protocol compatible UPS not found on %s", device_path); } /* * Read the commandline settings for the following parameters, since we can't * autodetect them. */ dstate_setinfo("ups.mfr", "%s", ((v = getval("manufacturer")) != NULL) ? v : "unknown"); dstate_setinfo("ups.model", "%s", ((v = getval("modelname")) != NULL) ? v : "unknown"); dstate_setinfo("ups.serial", "%s", ((v = getval("serialnumber")) != NULL) ? v : "unknown"); dstate_setinfo("ups.delay.start", "%d", 60 * ondelay); dstate_setinfo("ups.delay.shutdown", "%d", offdelay); /* * These are the instant commands we support. */ dstate_addcmd ("test.battery.start"); dstate_addcmd ("test.battery.stop"); dstate_addcmd ("test.failure.start"); dstate_addcmd ("test.failure.stop"); dstate_addcmd ("beeper.on"); dstate_addcmd ("beeper.off"); dstate_addcmd ("beeper.enable"); dstate_addcmd ("beeper.mute"); dstate_addcmd ("beeper.toggle"); dstate_addcmd ("shutdown.return"); dstate_addcmd ("shutdown.reboot"); upsh.instcmd = instcmd; } /* * The status polling commands are *almost* random. Whatever the reason * is, there is a certain pattern in them. The first character after the * start character 'Z' determines how many positions there are between * that character and the single 'L' character that's in each command (A=0, * B=1,...,J=9). The rest is filled with random (?) data [A...J]. But why? * No idea. The UPS *does* check if the polling commands match this format. * And as the SafeNet software uses "random" polling commands, so do we. * * Note: if you don't use ASCII, the characters will be different! */ void upsdrv_updateinfo(void) { char command[] = COM_POLL_STAT; int i; static int retry = 0; /* * Fill the command portion with random characters from the range * [A...J]. */ for (i = 1; i < 12; i++) { command[i] = (random() % 10) + 'A'; } /* * Find which character must be an 'L' and put it there. */ command[command[1]-'A'+2] = 'L'; /* * Do a status poll. */ if (safenet_command(command)) { ser_comm_fail("Status read failed"); if (retry < 2) { retry++; } else { dstate_datastale(); } return; } ser_comm_good(); retry = 0; if (ups.status.systemtest && ups.status.batterylow) { /* * Don't update status after stopping battery test, to * allow UPS to update the status flags (OB+LB glitch) */ if (safenet_command(COM_STOP_TEST)) { upslogx(LOG_WARNING, "Can't terminate battery test!"); } else { upslogx(LOG_INFO, "Battery test finished"); return; } } safenet_update(); dstate_dataok(); } void upsdrv_shutdown(void) { int retry = 3; /* * Since we may have arrived here before the hardware is initialized, * try to initialize it here. * * Initialize the serial interface of the UPS by sending the magic * string. If it does not respond with a valid status reply, * display an error message and give up. */ while (safenet_command(COM_INITIALIZE)) { if (--retry) { continue; } fatalx(EXIT_FAILURE, "SafeNet protocol compatible UPS not found on %s", device_path); } /* * Since the UPS will happily restart on battery, we must use a * different shutdown command depending on the line status, so * we need to check the status of the UPS here. */ if (ups.status.onbattery) { instcmd("shutdown.return", NULL); } else { instcmd("shutdown.reboot", NULL); } } void upsdrv_help(void) { } void upsdrv_makevartable(void) { addvar(VAR_VALUE, "manufacturer", "manufacturer [unknown]"); addvar(VAR_VALUE, "modelname", "modelname [unknown]"); addvar(VAR_VALUE, "serialnumber", "serialnumber [unknown]"); addvar(VAR_VALUE, "ondelay", "Delay before UPS startup (minutes)"); addvar(VAR_VALUE, "offdelay", "Delay before UPS shutdown (seconds)"); } void upsdrv_initups(void) { struct termios tio; const char *val; /* * Open and lock the serial port and set the speed to 1200 baud. */ upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B1200); if (tcgetattr(upsfd, &tio)) { fatal_with_errno(EXIT_FAILURE, "tcgetattr"); } /* * Use canonical mode input processing (to read reply line) */ tio.c_lflag |= ICANON; /* Canonical input (erase and kill processing) */ tio.c_cc[VEOF] = _POSIX_VDISABLE; tio.c_cc[VEOL] = '\r'; tio.c_cc[VERASE] = _POSIX_VDISABLE; tio.c_cc[VINTR] = _POSIX_VDISABLE; tio.c_cc[VKILL] = _POSIX_VDISABLE; tio.c_cc[VQUIT] = _POSIX_VDISABLE; tio.c_cc[VSUSP] = _POSIX_VDISABLE; tio.c_cc[VSTART] = _POSIX_VDISABLE; tio.c_cc[VSTOP] = _POSIX_VDISABLE; if (tcsetattr(upsfd, TCSANOW, &tio)) { fatal_with_errno(EXIT_FAILURE, "tcsetattr"); } /* * Set DTR and clear RTS to provide power for the serial interface. */ ser_set_dtr(upsfd, 1); ser_set_rts(upsfd, 0); val = getval("ondelay"); if (val) { ondelay = strtol(val, NULL, 10); } if ((ondelay < 0) || (ondelay > 9999)) { fatalx(EXIT_FAILURE, "Start delay '%d' out of range [0..9999]", ondelay); } val = getval("offdelay"); if (val) { offdelay = strtol(val, NULL, 10); } if ((offdelay < 0) || (offdelay > 999)) { fatalx(EXIT_FAILURE, "Shutdown delay '%d' out of range [0..999]", offdelay); } } void upsdrv_cleanup(void) { ser_set_dtr(upsfd, 0); ser_close(upsfd, device_path); }