/* * al175.c - NUT support for Eltek AL175 alarm module. * AL175 shall be in COMLI mode. * * Copyright (C) 2004 Marine & Bridge Navigation Systems * Copyright (C) 2004 Kirill Smelkov * * 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 #include #include #include #include "main.h" #include "serial.h" #include "al175.h" #define DEBUG 1 #define STX 0x02 #define ETX 0x03 #define ACK 0x06 typedef unsigned char byte_t; #if DEBUG #define XTRACE(fmt, ...) do { \ upsdebugx(1, "%s: " fmt, __FUNCTION__ __VA_ARGS__); \ } while (0) #else #define XTRACE(fmt, ...) do {} while (0) #endif /************ * RAW DATA * ************/ /** * raw_data buffer representation */ typedef struct { byte_t *buf; //!< the whole buffer address unsigned buf_size; //!< the whole buffer size byte_t *begin; //!< begin of content byte_t *end; //!< one-past-end of content } raw_data_t; /** * constant raw_data buffer representation */ #if 0 typedef struct { const byte_t *buf; //!< the whole buffer address unsigned buf_size; //!< the whole buffer size const byte_t *begin; //!< begin of content const byte_t *end; //!< one-past-end of content } const_raw_data_t; #else typedef raw_data_t const_raw_data_t; #endif /** * alloca raw_data buffer * @param size the size in bytes * @return alloca'ed memory as raw_data */ #define raw_alloca(size) \ ({ \ raw_data_t data; \ \ data.buf = alloca(size); \ data.buf_size = size; \ \ data.begin = data.buf; \ data.end = data.buf; \ \ data; \ }) /** * xmalloc raw buffer * @param size the size in bytes * @return xmalloc'ed memory as raw_data */ raw_data_t raw_xmalloc(size_t size) { raw_data_t data = { .buf = xmalloc(size), .buf_size = size, /* XXX: how to handle this? .begin = .buf, .end = .buf */ }; data.begin = data.buf; data.end = data.buf; return data; } /** * free raw_data buffer * @param buf the buffer to be freed */ void raw_free(raw_data_t *buf) { free(buf->buf); buf->buf = NULL; buf->buf_size = 0; buf->begin = NULL; buf->end = NULL; } /* taken from www.asciitable.com */ static const char* ascii_symb[] = { "NUL", /* 0x00 */ "SOH", /* 0x01 */ "STX", /* 0x02 */ "ETX", /* 0x03 */ "EOT", /* 0x04 */ "ENQ", /* 0x05 */ "ACK", /* 0x06 */ "BEL", /* 0x07 */ "BS", /* 0x08 */ "TAB", /* 0x09 */ "LF", /* 0x0A */ "VT", /* 0x0B */ "FF", /* 0x0C */ "CR", /* 0x0D */ "SO", /* 0x0E */ "SI", /* 0x0F */ "DLE", /* 0x10 */ "DC1", /* 0x11 */ "DC2", /* 0x12 */ "DC3", /* 0x13 */ "DC4", /* 0x14 */ "NAK", /* 0x15 */ "SYN", /* 0x16 */ "ETB", /* 0x17 */ "CAN", /* 0x18 */ "EM", /* 0x19 */ "SUB", /* 0x1A */ "ESC", /* 0x1B */ "FS", /* 0x1C */ "GS", /* 0x1D */ "RS", /* 0x1E */ "US" /* 0x1F */ }; /** * dump raw_data buffer to file stream * @param out output stream * @param buf the buffer to dump */ void raw_dump(FILE *out, const_raw_data_t buf) { byte_t *p; fprintf(out, "( "); for (p=buf.begin; p!=buf.end; ++p) if (*p < 0x20) fprintf(out, "%4s ", ascii_symb[*p]); else fprintf(out, "'%c' ", *p); fprintf(out, ")\t[ "); for (p=buf.begin; p!=buf.end; ++p) fprintf(out, "0x%02x ", *p); fprintf(out, "]\n"); } /***************************************************************************/ /*************** * COMLI types * ***************/ /** * COMLI message header info * @see 1. INTRODUCTION */ typedef struct { int id; //!< Id[1:2] int stamp; //!< Stamp[3] int type; //!< Mess Type[4] } msg_head_t; /** * COMLI IO header info * @see 1. INTRODUCTION */ typedef struct { unsigned addr; //!< Addr[5:8] unsigned len; //!< NOB[9:10] } io_head_t; /** * maximum allowed io.len value */ #define IO_LEN_MAX 0xff /** * COMLI header info * @see 1. INTRODUCTION */ typedef struct { msg_head_t msg; //!< message header [1:4] io_head_t io; //!< io header [5:10] } comli_head_t; /****************** * MISC UTILITIES * ******************/ /** * convert hex string to int * @param head input string * @param count string length * @return parsed value (>=0) if success, -1 on error */ static long from_hex(const char *head, unsigned len) { long val=0; while (len-- != 0) { int ch = *head; if (!isxdigit(ch)) return -1; /* wrong character */ val *= 0x10; ch = toupper(ch); /* ? locale ? */ if (isdigit(ch)) val += (ch-'0'); else val += 0x0a + (ch-'A'); ++head; } return val; } /** * compute checksum of a buffer * @see 10. CHECKSUM BCC * @param buf buffer address * @param count no. of bytes in the buffer * @return computed checksum */ static byte_t compute_bcc(const void *buf, size_t count) { byte_t bcc=0; unsigned i; for (i=0; i> 7 ) | \ ( ((x) & 0x40) >> 5 ) | \ ( ((x) & 0x20) >> 3 ) | \ ( ((x) & 0x10) >> 1 ) | \ ( ((x) & 0x08) << 1 ) | \ ( ((x) & 0x04) << 3 ) | \ ( ((x) & 0x02) << 5 ) | \ ( ((x) & 0x01) << 7 ) ) /** * reverse bits in a buffer * @param buf buffer address * @param count no. of bytes in the buffer */ static void reverse_bits(void *buf, size_t count) { byte_t *mem = buf; while (count!=0) { *mem = REVERSE_BITS(*mem); ++mem; --count; } } /********************************************************************/ /* * communication basics * * ME (Monitor Equipment) * PRS (? Power System ? ) * * there are 2 types of transactions: * * 'ACTIVATE COMMAND' * ME -> PRS (al_prep_activate) * ME <- PRS [ack] (al_check_ack) * * * 'READ REGISTER' * ME -> PRS (al_prep_read_req) * ME <- PRS [data] (al_parse_reply) * */ /******************** * COMLI primitives * ********************/ /************************ * COMLI: OUTPUT FRAMES * ************************/ /** * prepare COMLI sentence * @see 1. INTRODUCTION * @param dest [out] where to put the result * @param h COMLI header info * @param buf data part of the sentence * @param count amount of data bytes in the sentence] * * @note: the data are copied into the sentence "as-is", that is no conversion is done. * if the coller want to reevrse bits it is necessary to call reverse_bits(...) prior * to comli_prepare. */ static void comli_prepare(raw_data_t *dest, const comli_head_t *h, const void *buf, size_t count) { /* * 0 1 2 3 4 5 6 7 8 9 10 11 - - - N-1 N * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+ * | STX | IDh IDl | Stamp | type | addr1 addr2 addr3 addr4 | NOBh NOBl | ...data... | ETX | BCC | * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+ * * ^ ^ * | | *begin end */ byte_t *out = dest->begin; /* it's caller responsibility to allocate enough space. else it is a bug in the program */ if ( (out+11+count+2) > (dest->buf + dest->buf_size) ) fatalx(EXIT_FAILURE, "too small dest in comli_prepare\n"); out[0] = STX; snprintf(out+1, 10+1, "%02X%1i%1i%04X%02X", h->msg.id, h->msg.stamp, h->msg.type, h->io.addr, h->io.len); memcpy(out+11, buf, count); reverse_bits(out+11, count); out[11+count] = ETX; out[12+count] = compute_bcc(out+1, 10+count+1); dest->end = dest->begin + (11+count+2); } /** * prepare AL175 read data request * @see 2. MESSAGE TYPE 2 (COMMAND SENT FROM MONITORING EQUIPMENT) * @param dest [out] where to put the result * @param addr start address of requested area * @param count no. of requested bytes */ static void al_prep_read_req(raw_data_t *dest, unsigned addr, size_t count) { comli_head_t h = { .msg = { .id = 0x14, .stamp = 1, .type = 2, }, .io = { .addr = addr, .len = count, }, }; return comli_prepare(dest, &h, NULL, 0); } /** * prepare AL175 activate command * @see 4. MESSAGE TYPE 0 (ACTIVATE COMMAND) * @param dest [out] where to put the result * @param cmd command type [11] * @param subcmd command subtype [12] * @param pr1 first parameter [13:14] * @param pr2 second parameter [15:16] * @param pr3 third parameter [17:18] */ static void al_prep_activate(raw_data_t *dest, int cmd, int subcmd, int pr1, int pr2, int pr3) { comli_head_t h = { .msg = { .id = 0x14, .stamp = 1, .type = 0, }, .io = { .addr = 0x4500, .len = 8, }, }; char data[8+1]; data[0] = cmd; // XXX: shall be ASCII here data[1] = subcmd; snprintf(data+2, 6+1, "%2X%2X%2X", pr1, pr2, pr3); return comli_prepare(dest, &h, data, 8); } /*********************** * COMLI: INPUT FRAMES * ***********************/ /** * check COMLI frame for correct layout and bcc * @param f frame to check * * @return 0 (ok) -1 (error) */ static int comli_check_frame(const_raw_data_t f) { int bcc; byte_t *tail; if (*f.begin!=STX) return -1; tail = f.end - 2; if (tail <= f.begin) return -1; if (tail[0]!=ETX) return -1; bcc = compute_bcc(f.begin+1, (f.end - f.begin) - 2/*STX & BCC*/); if (bcc!= tail[1]) return -1; return 0; } /** * parse reply header from PRS * @see 3. MESSAGE TYPE 0 (REPLY FROM PRS ON MESSAGE TYPE 2) * * @param io [out] parsed io_header * @param raw_reply_head [in] raw reply header from PRS * @return 0 (ok), -1 (error) * * @see al_parse_reply */ static int al_parse_reply_head(io_head_t *io, const raw_data_t raw_reply_head) { /* * 0 1 2 3 4 5 6 7 8 9 10 * +-----+---------+-------+------+-------------------------+-----------+-----------+ * | STX | IDh IDl | Stamp | type | addr1 addr2 addr3 addr4 | NOBh NOBl | ......... | * +-----+---------+-------+------+-------------------------+-----------+-----------+ * * ^ ^ * | | * begin end */ unsigned long io_addr, io_len; const byte_t *reply_head = raw_reply_head.begin - 1; if ( (raw_reply_head.end - raw_reply_head.begin) != 10) { XTRACE("wrong size"); return -1; /* wrong size */ } if (reply_head[1]!='0' || reply_head[2]!='0') { XTRACE("wrong id"); return -1; /* wrong id */ } if (reply_head[3]!='1') { XTRACE("wrong stamp"); return -1; /* wrong stamp */ } if (reply_head[4]!='0') { XTRACE("wrong type"); return -1; /* wrong type */ } io_addr = from_hex(&reply_head[5], 4); if (io_addr==-1UL) { XTRACE("wrong addr"); return -1; /* wrong addr */ } io_len = from_hex(&reply_head[9], 2); if (io_len==-1UL) { XTRACE("wrong nob"); return -1; /* wrong NOB */ } if (io_len > IO_LEN_MAX) { XTRACE("nob too big"); return -1; /* too much data claimed */ } io->addr = io_addr; io->len = io_len; return 0; } /** * parse reply from PRS * @see 3. MESSAGE TYPE 0 (REPLY FROM PRS ON MESSAGE TYPE 2) * @param io_head [out] parsed io_header * @param io_buf [in] [out] raw_data where to place incoming data (see ...data... below) * @param raw_reply raw reply from PRS to check * @return 0 (ok), -1 (error) * * @see al_parse_reply_head */ static int al_parse_reply(io_head_t *io_head, raw_data_t *io_buf, const_raw_data_t raw_reply) { /* * 0 1 2 3 4 5 6 7 8 9 10 11 - - - N-1 N * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+ * | STX | IDh IDl | Stamp | type | addr1 addr2 addr3 addr4 | NOBh NOBl | ...data... | ETX | BCC | * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+ * * ^ ^ * | | * begin end */ int err; unsigned i; const byte_t *reply = raw_reply.begin - 1; /* 1: extract header and parse it */ const_raw_data_t raw_reply_head = raw_reply; if (raw_reply_head.begin + 10 <= raw_reply_head.end) raw_reply_head.end = raw_reply_head.begin + 10; err = al_parse_reply_head(io_head, raw_reply_head); if (err==-1) return -1; /* 2: process data */ reply = raw_reply.begin - 1; if ( (raw_reply.end - raw_reply.begin) != (ptrdiff_t)(10 + io_head->len)) { XTRACE("corrupt sentence"); return -1; /* corrupt sentence */ } /* extract the data */ if (io_buf->buf_size < io_head->len) { // XXX: maybe write from io_buf->begin ? XTRACE("too much data to fit in io_buf."); return -1; /* too much data to fit in io_buf */ } io_buf->begin = io_buf->buf; // XXX: see ^^^ io_buf->end = io_buf->begin; for (i=0; ilen; ++i) *(io_buf->end++) = reply[11+i]; reverse_bits(io_buf->begin, (io_buf->end - io_buf->begin) ); #if DEBUG fprintf(stderr, "rx_buf:\t\t"); raw_dump(stderr, *io_buf); #endif return 0; /* all ok */ } /** * check acknowledge from PRS * @see 5. ACKNOWLEDGE FROM PRS * @param raw_ack raw acknowledge from PRS to check * @return 0 on success, -1 on error */ static int al_check_ack(const_raw_data_t raw_ack) { /* * 0 1 2 3 4 5 6 7 * +-----+---------+-------+------+-----+-----+-----+ * | STX | IDh IDl | Stamp | type | ACK | ETX | BCC | * +-----+---------+-------+------+-----+-----+-----+ * * ^ ^ * | | * begin end */ const byte_t *ack = raw_ack.begin - 1; if ( (raw_ack.end - raw_ack.begin) !=5) { XTRACE("wrong size"); return -1; /* wrong size */ } if (ack[1]!='0' || ack[2]!='0') { XTRACE("wrong id"); return -1; /* wrong id */ } /* the following in not mandated. it is just said it will be * "same as one received". but we always send '1' (0x31) as stamp * (see 4. MESSAGE TYPE 0 (ACTIVATE COMMAND). Hence, stamp checking * is hardcoded here. */ if (ack[3]!='1') { XTRACE("wrong stamp"); return -1; /* wrong stamp */ } if (ack[4]!='1') { XTRACE("wrong type"); return -1; /* wrong type */ } if (ack[4]!=ACK) { XTRACE("wrong ack"); return -1; /* wrong ack */ } return 0; } /******************************************************************/ /********** * SERIAL * **********/ static void alarm_handler(int sig) { /* just do nothing */ XTRACE("timeout...\n"); /* XXX: doing so is wrong from signal handler... */ } /* clear any flow control (stolen from powercom.c) */ static void ser_disable_flow_control (void) { struct termios tio; tcgetattr (upsfd, &tio); tio.c_iflag &= ~ (IXON | IXOFF); tio.c_cc[VSTART] = _POSIX_VDISABLE; tio.c_cc[VSTOP] = _POSIX_VDISABLE; upsdebugx(2, "Flow control disable"); /* disable any flow control */ tcsetattr(upsfd, TCSANOW, &tio); } static void flush_rx_queue() { ser_flush_in(upsfd, "", 1/*verbose*/); } /** * transmit frame to PRS * * @param frame the frame to tansmit * @return 0 (ok) -1 (error) */ static int tx(const_raw_data_t frame) { int err; #if DEBUG fprintf(stderr, "tx:\t\t"); raw_dump(stderr, frame); #endif err = ser_send_buf(upsfd, frame.begin, (frame.end - frame.begin) ); if (err==-1) { upslogx(LOG_ERR, "failed to send frame to PRS: %s", strerror(errno)); return -1; } if (err != (frame.end - frame.begin)) { upslogx(LOG_ERR, "sent incomplete frame to PRS"); return -1; } return 0; } /*********** * CHATTER * ***********/ static int get_char(char *ch) { /* 999 here means infinity. * all timeouts are processed via alarm(2) */ return ser_get_char(upsfd, ch, 999, 0); } static int get_buf(char *buf, size_t len) { return ser_get_buf_len(upsfd, buf, len, 999, 0); } /** * scan incoming bytes for specific character * * @return 0 (got it) -1 (error) */ static int scan_for(char c) { char in; int err; while (1) { err = get_char(&in); if (err==-1) return -1; if (in==c) break; } return 0; } /** * receive 'activate command' ACK from PRS * * @return 0 (ok) -1 (error) */ static int recv_command_ack() { int err; raw_data_t ack; /* 1: STX */ err = scan_for(STX); if (err==-1) return -1; ack = raw_alloca(8); *(ack.end++) = STX; /* 2: ID1 ID2 STAMP MSG_TYPE ACK ETX BCC */ err = get_buf(ack.end, 7); if (err!=7) return -1; ack.end += 7; /* frame constructed - let's verify it */ #if DEBUG fprintf(stderr, "rx:\t\t"); raw_dump(stderr, ack); #endif /* generic layout */ err = comli_check_frame(ack); if (err==-1) return -1; /* shrink frame */ ack.begin += 1; ack.end -= 2; return al_check_ack(ack); } /** * receive 'read register' data from PRS * @param io [out] io header of received data * @param io_buf [in] [out] where to place incoming data * * @return 0 (ok) -1 (error) */ static int recv_register_data(io_head_t *io, raw_data_t *io_buf) { int err; raw_data_t reply_head; raw_data_t reply; /* 1: STX */ err = scan_for(STX); if (err==-1) return -1; reply_head = raw_alloca(11); *(reply_head.end++) = STX; /* 2: ID1 ID2 STAMP MSG_TYPE ADDR1 ADDR2 ADDR3 ADDR4 LEN1 LEN2 */ err = get_buf(reply_head.end, 10); if (err!=10) return -1; reply_head.end += 10; #if DEBUG fprintf(stderr, "rx_head:\t"); raw_dump(stderr, reply_head); #endif /* 3: check header, extract IO info */ reply_head.begin += 1; /* temporarily strip STX */ err = al_parse_reply_head(io, reply_head); if (err==-1) return -1; reply_head.begin -= 1; /* restore STX */ #if DEBUG fprintf(stderr, "io: %x/%x\n", io->addr, io->len); #endif /* 4: allocate space for full reply and copy header there */ reply = raw_alloca(11/*head*/ + io->len/*data*/ + 2/*ETX BCC*/); memcpy(reply.end, reply_head.begin, (reply_head.end - reply_head.begin)); reply.end += (reply_head.end - reply_head.begin); /* 5: receive tail of the frame */ err = get_buf(reply.end, io->len + 2); if (err!=(int)(io->len+2)) { XTRACE("rx_tail failed"); return -1; } reply.end += io->len + 2; /* frame constructed, let's verify it */ #if DEBUG fprintf(stderr, "rx:\t\t"); raw_dump(stderr, reply); #endif /* generic layout */ err = comli_check_frame(reply); if (err==-1) { XTRACE("corrupt frame"); return -1; } /* shrink frame */ reply.begin += 1; reply.end -= 2; // XXX: a bit of processing duplication here return al_parse_reply(io, io_buf, reply); } /*****************************************************************/ /********************* * AL175: DO COMMAND * *********************/ /** * do 'ACTIVATE COMMAND' * * @return 0 (ok) -1 (error) */ static int al175_do(int cmd, int subcmd, int pr1, int pr2, int pr3) { int err; raw_data_t CTRL_frame = raw_alloca(512); al_prep_activate(&CTRL_frame, cmd, subcmd, pr1, pr2, pr3); flush_rx_queue(); /* DROP */ err = tx(CTRL_frame); /* TX */ if (err==-1) return -1; return recv_command_ack(); /* RX */ } /** * 'READ REGISTER' * */ static int al175_read(byte_t *dst, unsigned addr, size_t count) { int err; raw_data_t REQ_frame = raw_alloca(512); raw_data_t rx_data; io_head_t io; al_prep_read_req(&REQ_frame, addr, count); flush_rx_queue(); /* DROP */ err = tx(REQ_frame); /* TX */ if (err==-1) return -1; rx_data.buf = dst; rx_data.buf_size = count; rx_data.begin = dst; rx_data.end = dst; err = recv_register_data(&io, &rx_data); if (err==-1) return -1; if (rx_data.begin != dst) // XXX: paranoia return -1; if ((rx_data.end - rx_data.begin) != (int)count) return -1; if ( (io.addr != addr) || (io.len != count) ) { XTRACE("io_head mismatch"); return -1; } return 0; } /************* * NUT STUFF * *************/ /**************************** * ACTIVATE COMMANDS table * * see 8. ACTIVATE COMMANDS */ // XXX: ? typedef int mm_t; /* minutes */ typedef int VV_t; /* voltage */ #define Z1 , 0 #define Z2 , 0, 0 #define Z3 , 0, 0, 0 #define ACT static __attribute__((used)) int ACT TOGGLE_PRS_ONOFF () { return al175_do(0x81, 0x80 Z3); } ACT CANCEL_BOOST () { return al175_do(0x82, 0x80 Z3); } ACT STOP_BATTERY_TEST () { return al175_do(0x83, 0x80 Z3); } ACT START_BATTERY_TEST (VV_t EndVolt, unsigned Minutes) { return al175_do(0x83, 0x81, EndVolt, Minutes Z1); } ACT SET_FLOAT_VOLTAGE (VV_t v) { return al175_do(0x87, 0x80, v Z2); } ACT SET_BOOST_VOLTAGE (VV_t v) { return al175_do(0x87, 0x81, v Z2); } ACT SET_HIGH_BATTERY_LIMIT (VV_t Vhigh) { return al175_do(0x87, 0x82, Vhigh Z2); } ACT SET_LOW_BATTERY_LIMIT (VV_t Vlow) { return al175_do(0x87, 0x83, Vlow Z2); } ACT SET_DISCONNECT_LEVEL_AND_DELAY (VV_t level, mm_t delay) { return al175_do(0x87, 0x84, level, delay Z1); } ACT RESET_ALARMS () { return al175_do(0x88, 0x80 Z3); } ACT CHANGE_COMM_PROTOCOL () { return al175_do(0x89, 0x80 Z3); } ACT SET_VOLTAGE_AT_ZERO_T (VV_t v) { return al175_do(0x8a, 0x80, v Z2); } ACT SET_SLOPE_AT_ZERO_T (VV_t mv_per_degree) { return al175_do(0x8a, 0x81, mv_per_degree Z2); } ACT SET_MAX_TCOMP_VOLTAGE (VV_t v) { return al175_do(0x8a, 0x82, v Z2); } ACT SET_MIN_TCOMP_VOLTAGE (VV_t v) { return al175_do(0x8a, 0x83, v Z2); } ACT SWITCH_TEMP_COMP (int on) { return al175_do(0x8b, 0x80, on Z2); } // XXX: ? ACT SWITCH_SYM_ALARM () { return al175_do(0x8c, 0x80 Z3); } /** * extract double value from a word */ static double d16(byte_t data[2]) { return (data[1] + 0x100*data[0]) / 100.0; } void upsdrv_updateinfo(void) { /* int flags; */ /* char temp[256]; */ byte_t x4000[9]; /* registers from 0x4000 to 0x4040 inclusive */ byte_t x4048[2]; /* 0x4048 - 0x4050 */ byte_t x4100[8]; /* 0x4100 - 0x4138 */ byte_t x4180[8]; /* 0x4180 - 0x41b8 */ byte_t x4300[2]; /* 0x4300 - 0x4308 */ int err; double batt_current = 0.0; fprintf(stderr, "\nUPDATEINFO\n"); alarm(3); #define RECV(reg) do { \ err = al175_read(x ## reg, 0x ## reg, sizeof(x ## reg)); \ if (err==-1) { \ dstate_datastale(); \ alarm(0); \ return; \ } \ } while (0) RECV(4000); RECV(4048); RECV(4100); RECV(4180); RECV(4300); status_init(); #if 0 /* non conformant with NUT naming */ // 0x4000 DIGITAL INPUT 1-8 dstate_setinfo("load.fuse", (x4000[0] & 0x80) ? "OK" : "BLOWN"); dstate_setinfo("battery.fuse", (x4000[0] & 0x40) ? "OK" : "BLOWN"); dstate_setinfo("symalarm.fuse", (x4000[0] & 0x20) ? "OK" : "BLOWN"); // 0x4008 BATTERY INFORMATION dstate_setinfo("battery.contactor", (x4000[1] & 0x80) ? "XX" : "YY"); // FIXME dstate_setinfo("load.contactor", (x4000[1] & 0x40) ? "XX" : "YY"); // FIXME dstate_setinfo("lvd.contactor", (x4000[1] & 0x20) ? "XX" : "YY"); // FIXME #endif if (x4000[1] & 0x01) // battery test running status_set("TEST"); // TODO: others from 0x4008 // 0x4010 NOT USED // 0x4018 NOT USED switch (x4000[4]) { // 0x4020 MAINS VOLTAGE STATUS case 0: status_set("OL"); break; case 1: status_set("OB"); break; case 2: status_set("NOT_APPLICABLE"); break; default: status_set("OFF"); // XXX: "not applicable" == OFF ? } // 0x4028 SYSTEM ON OFF STATUS // XXX: 0x4028 is broken? (it reads as 0x55) switch (x4000[5]) { case 0: break; case 1: status_set("OFF"); break; default: status_set("UNKNOWN..."); } switch (x4000[6]) { // 0x4030 BATTERY TEST FAIL case 0: dstate_setinfo("ups.test.result", "OK"); break; case 1: status_set("RB"); dstate_setinfo("ups.test.result", "FAIL"); break; // XXX: RB == remove battery? /* AQU note: the below must be adapted! */ default: status_set("?T"); } switch (x4000[7]) { // 0x4038 BATTERY VOLTAGE STATUS case 0: /* normal */ break; case 1: status_set("LB"); break; #if 0 /* non conformant to ups.status values */ case 2: status_set("HB"); break; default: status_set("?B"); #endif default: break; } switch (x4000[8]) { // 0x4040 POS./NEG. BATT. CURRENT case 0: batt_current = +1.0; break; // positive case 1: batt_current = -1.0; break; // negative default: batt_current = 0.0; // shouldn't happen } switch (x4048[0]) { // 0x4048 BOOST STATUS case 0: /* no boost */; break; case 1: status_set("BOOST"); break; #if 0 /* non conformant to ups.status values */ default: status_set("BOO??"); #endif default: break; } { const char *v; switch (x4048[1]) { // 0x4050 SYSTEM VOLTAGE STAT. case 0: v = "48"; break; case 1: v = "24"; break; case 2: v = "12"; break; case 3: v = "26"; break; case 4: v = "60"; break; default: v = "??V"; } dstate_setinfo("output.voltage.nominal", "%s", v); } // 0x4100 BATTERY VOLTAGE REF dstate_setinfo("battery.voltage.nominal", "%.2f", d16(x4100+0)); // 0x4110 BOOST VOLTAGE REF dstate_setinfo("input.transfer.boost.low", "%.2f", d16(x4100+2)); // XXX: boost.high ? // 0x4120 HIGH BATT VOLT REF XXX // 0x4130 LOW BATT VOLT REF XXX // 0x4180 FLOAT VOLTAGE XXX // 0x4190 BATT CURRENT batt_current *= d16(x4180+2); dstate_setinfo("battery.current", "%.2f", batt_current); // 0x41b0 LOAD CURRENT (output.current in NUT) dstate_setinfo("output.current", "%.2f", d16(x4180+6)); // 0x4300 BATTERY TEMPERATURE dstate_setinfo("battery.temperature", "%.2f", d16(x4300+0)); status_commit(); upsdebugx(1, "STATUS: %s", dstate_getinfo("ups.status")); dstate_dataok(); /* out: */ alarm(0); return; } void upsdrv_shutdown(void) { /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ /* maybe try to detect the UPS here, but try a shutdown even if it doesn't respond at first if possible */ /* replace with a proper shutdown function */ fatalx(EXIT_FAILURE, "shutdown not supported"); /* TODO: implement... */ /* you may have to check the line status since the commands for toggling power are frequently different for OL vs. OB */ /* OL: this must power cycle the load if possible */ /* OB: the load must remain off until the power returns */ } static int instcmd_worker(const char *cmdname) { /* * test.battary.start * test.battary.stop */ if (!strcasecmp(cmdname, "test.battery.start")) { START_BATTERY_TEST(24, 1); return STAT_INSTCMD_HANDLED; } if (!strcasecmp(cmdname, "test.battery.stop")) { STOP_BATTERY_TEST(); return STAT_INSTCMD_HANDLED; } upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname); return STAT_INSTCMD_UNKNOWN; } static int instcmd(const char *cmdname, const char *extra) { int err; upsdebugx(1, "INSTCMD: %s", cmdname); alarm(5); err = instcmd_worker(cmdname); alarm(0); return err; } void upsdrv_help(void) { } /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { /* allow '-x xyzzy' */ /* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */ /* allow '-x foo=' */ /* addvar(VAR_VALUE, "foo", "Override foo setting"); */ } void upsdrv_banner(void) { printf("Network UPS Tools - Eltek AL175/COMLI support module %s (%s)\n\n", DRV_VERSION, UPS_VERSION); /* * This driver does not support upsdrv_shutdown(), which makes * it not very useful in a real world application. This alone * warrants 'experimental' status, but for the below mentioned * reasons (to name a few), it's flagged 'broken' instead. * * - ‘return’ with a value, in function returning void (2x) * - anonymous variadic macros were introduced in C99 * - C++ style comments are not allowed in ISO C90 * - ISO C forbids braced-groups within expressions (5x) * - ISO C90 forbids specifying subobject to initialize (16x) * - ISO C99 requires rest arguments to be used (18x) * - initializer element is not computable at load time (4x) * * In short, there is a lot of rewriting to be done. Not the * whole world is a Linux box with the latest gcc on it. */ broken_driver = 1; } void upsdrv_initups(void) { signal(SIGALRM, alarm_handler); alarm(0); upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B9600); ser_disable_flow_control(); /* probe ups type */ /* to get variables and flags from the command line, use this: * * first populate with upsdrv_buildvartable above, then... * * set flag foo : /bin/driver -x foo * set variable 'cable' to '1234' : /bin/driver -x cable=1234 * * to test flag foo in your code: * * if (testvar("foo")) * do_something(); * * to show the value of cable: * * if ((cable == getval("cable"))) * printf("cable is set to %s\n", cable); * else * printf("cable is not set!\n"); * * don't use NULL pointers - test the return result first! */ /* the upsh handlers can't be done here, as they get initialized * shortly after upsdrv_initups returns to main. */ /* don't try to detect the UPS here */ } void upsdrv_cleanup(void) { /* free(dynamic_mem); */ ser_close(upsfd, device_path); } void upsdrv_initinfo(void) { /* try to detect the UPS here - call fatal_with_errno(EXIT_FAILURE, ) if it fails */ dstate_setinfo("driver.version.internal", "%s", DRV_VERSION); dstate_setinfo("ups.mfr", "Eltek"); dstate_setinfo("ups.model", "AL175"); /* ... */ /* instant commands */ dstate_addcmd ("test.battery.start"); dstate_addcmd ("test.battery.stop"); /* XXX: more? */ upsh.instcmd = instcmd; } /* vim: set ts=8 noet sts=8 sw=8 */