/* toshiba1500.c - model specific routines for Toshiba 1500 series units Copyright (C) 2009 Scott Mace Copyright (C) 2000 Kenneth Porter Copyright (C) 1999 Russell Kroll New driver for Nut 2.4.1 -smace This new style driver has only been testing on Toshiba 1400XL UPS Based on fentonups.c by Russell Kroll. Thanks go to Dennis Kruep and Greg Mack of Toshiba for making the protocol documentation available. 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" #define TOSHIBA_1400XL 1 #define DRIVER_NAME "Toshiba 1500 UPS Driver" #define DRIVER_VERSION "0.1" /* character codes */ #define STX 0x02 #define ETX 0x03 #define EOT 0x04 #define ENQ 0x05 #define ACK 0x06 #define NAK 0x15 /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Scott Mace \n", DRV_STABLE, { NULL } }; /* retry sending message 3 times until ACK received or timeout * return 0 on ACK, -1 on timeout */ int waitforack (const char* message) { int try_count; for (try_count = 0; try_count < 3; ++try_count) { char temp[4]; ser_send_pace (upsfd, 10000, "%s", message); usleep (200000); if( ser_get_line (upsfd, temp, 3, ACK, "", 3, 0) > 0) { return 0; } } return -1; /* retries exhausted */ } void updatestatus (unsigned status) { static unsigned last_status = 0; upsdebugx(1, "updatestatus(%X)", status); if (status != last_status) { last_status = status; status_init (); if (0x10 & status) /* input undervoltage or blackout */ status_set("OB"); else status_set("OL"); if (0x08 & status) /* low battery */ status_set("LB"); if (0x04 & status) /* bypass operation */ status_set("BYPASS"); if (0x02 & status) /* inverter operation */ status_set("INV"); if (0x01 & status) /* synchronous operation */ status_set("SYNC"); /* todo: check 0x20 bit (fault) and read fault word * trim trailing blank */ status_commit(); } } /* return 0 on success */ int select_toshiba (void) { /* send 1-1-ENQ ("select") */ if (-1 == waitforack ("11\005")) { upsdebugx(1, "Bad reply to select"); return -1; } return 0; } /* create value selection command followed by BCC */ void compose_toshiba_command (char* buf, int buflen, char command_type, const char* address) { char bcc; char* p; snprintf (buf, buflen - 1, "\002%c%s\003", command_type, address); /* STX does not participate in BCC computation */ bcc = 0; for (p = buf + 1; *p; ++p) bcc ^= *p; /* append BCC */ *p++ = bcc; *p = '\0'; } /* return -1 on failure, 0 on success * stores null-terminated result string in buf, * stores status byte in *status */ int get_toshiba_result (char buf[5], char* status, char command_type, const char* address) { char temp[10]; char* statusp; unsigned char bcc; int ret; char* p; /* wait for up to 10 chars of response */ if (ser_get_line (upsfd, temp, sizeof temp, ETX, "", 5, 0) < 1) { return -1; } /* attempt to parse value */ if (STX != temp[0] || command_type != temp[1] || address[0] != temp[2] || address[1] != temp[3]) { upsdebugx(1, "prefix wrong"); return -1; } /* find the ETX (preceded by UPS status, followed by BCC) */ /* check BCC */ ret = ser_get_buf_len (upsfd, &bcc, 1, 3, 0); if (ret < 0) return -1; for (p = temp + 1; *p; ++p) bcc ^= *p; bcc ^= ETX; /* account for ETX eaten by upsrecv */ if (bcc) { upsdebugx(1, "bad BCC, result = %02X", bcc); return -1; } /* looks good, store it */ statusp = temp + strlen (temp) - 1; /* status at end of message */ *status = *statusp; *statusp = '\0'; /* terminate result string */ strcpy (buf, temp + 4); return 0; } /* query a value from the UPS and update status bits * returns value (0-999) on success, -1 on failure */ int pollvalue (const char* address) { char temp[10]; unsigned try_count; unsigned value; char status; upsdebugx(1, "pollvalue(%s)", address); memset (temp, '\0', sizeof temp); select_toshiba (); /* send command */ compose_toshiba_command (temp, sizeof temp, 'M', address); /* now send command and wait for ACK */ if (-1 == waitforack (temp)) { upsdebugx(1, "Bad reply to command"); return -1; } ser_send_char (upsfd, EOT); /* done selecting */ /* now poll for value */ ser_send_pace (upsfd, 10000, "%s", "10\005"); /* 1-0-ENQ */ for (try_count = 0; try_count < 3; ++try_count) { if (-1 == get_toshiba_result (temp, &status, 'M', address)) { ser_send_pace (upsfd, 10000, "%s", "10\025"); /* 1-0-NAK */ continue; } break; /* result ok */ } ser_send_char (upsfd, EOT); /* signal UPS we're quitting */ if (3 == try_count) { upsdebugx(1, "giving up on parameter %s\n", address); return -1; } updatestatus (status); /* status at end of message */ sscanf (temp,"%u",&value); upsdebugx(1, "parameter %s has value '%s'\n", address, temp); return value; } /* initialize information */ void upsdrv_initinfo (void) { /* write constant data for this model */ dstate_setinfo ("ups.mfr", "Toshiba"); #ifdef TOSHIBA_1400XL dstate_setinfo ("ups.model", "1400XL Plus"); #else dstate_setinfo ("ups.model", "1500 Series"); #endif } /* update information */ void upsdrv_updateinfo (void) { unsigned value; /* parsed result */ double dvalue; /* same, but as a double */ #ifdef 0 /* fault bits */ pollvalue ("05"); /* no poll result on 1500 */ #endif /* output voltage in percent */ dvalue = 240 * pollvalue ("10"); if (dvalue >= 0) { /* convert % to volts of rated voltage */ dstate_setinfo("output.voltage", "%4.1f", dvalue / 100.0); } #ifdef TOSHIBA_1400XL /* input voltage in percent */ dvalue = 240 * pollvalue ("20"); if (dvalue >= 0) { /* convert % to volts of rated voltage */ dstate_setinfo("input.voltage", "%4.1f", dvalue / 100.0); } /* can't select on 1500 */ #endif /* output current in percent (not used) */ value = pollvalue ("30"); if (value >= 0) { dstate_setinfo("ups.load", "%u", value); } /* battery voltage in percent */ value = pollvalue ("40"); if (value >= 0) { dstate_setinfo("battery.charge", "%u", value); } /* input frequency in Hz * 10 */ dvalue = pollvalue ("50") / 10.0; if (dvalue >= 0) { dstate_setinfo("input.frequency", "%3.1f", dvalue); } #ifdef TOSHIBA_1400XL /* output frequency in Hz * 10 (not used) */ dvalue = pollvalue ("51") / 10.0; if (dvalue >= 0) { dstate_setinfo("output.frequency", "%3.1f", dvalue); } /* can't select on 1500 */ #endif dstate_dataok(); } void upsdrv_help(void) { } void upsdrv_makevartable(void) { } void upsdrv_initups(void) { struct termios tio; upsfd = ser_open(device_path); /* switch to 7E1 */ tcgetattr (upsfd, &tio); tio.c_cflag = CS7 | CLOCAL | CREAD | PARENB; #ifdef HAVE_CFSETISPEED cfsetispeed (&tio, B1200); cfsetospeed (&tio, B1200); #endif tcsetattr (upsfd, TCSANOW, &tio); } void upsdrv_cleanup(void) { ser_close(upsfd, device_path); } void upsdrv_shutdown(void) { fatalx(EXIT_FAILURE, "shutdown not supported"); }