1 /*
2 * al175.c - NUT support for Eltek AL175 alarm module.
3 * AL175 shall be in COMLI mode.
4 *
5 * Copyright (C) 2004-2013 Marine & Bridge Navigation Systems <http://mns.spb.ru>
6 * Author: Kirill Smelkov <kirr@mns.spb.ru>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
22
23
24 /*
25 * - NOTE the following document is referenced in this driver:
26 *
27 * TE-36862-B4 "COMLI COMMUNICATION PROTOCOL IMPLEMENTED IN PRS SYSTEMS",
28 * by Eltek A/S
29 *
30 *
31 * - AL175 debug levels:
32 *
33 * 1 user-level trace (status, instcmd, etc...)
34 * 2 status decode errors
35 * 3 COMLI proto handling errors
36 * 4 raw IO trace
37 *
38 */
39
40 #include "main.h"
41 #include "serial.h"
42 #include "timehead.h"
43
44 #include <stddef.h>
45 #include <ctype.h>
46 #include <stdlib.h>
47 #include <stdio.h>
48 #include <unistd.h>
49
50
51 #include "nut_stdint.h"
52 typedef uint8_t byte_t;
53
54
55 #define DRIVER_NAME "Eltek AL175/COMLI driver"
56 #define DRIVER_VERSION "0.12"
57
58 /* driver description structure */
59 upsdrv_info_t upsdrv_info = {
60 DRIVER_NAME,
61 DRIVER_VERSION,
62 "Kirill Smelkov <kirr@mns.spb.ru>\n" \
63 "Marine & Bridge Navigation Systems <http://mns.spb.ru>",
64 DRV_EXPERIMENTAL,
65 { NULL }
66 };
67
68
69 #define STX 0x02
70 #define ETX 0x03
71 #define ACK 0x06
72
73
74 /************
75 * RAW DATA *
76 ************/
77
78 /**
79 * raw_data buffer representation
80 */
81 typedef struct {
82 byte_t *buf; /*!< the whole buffer address */
83 unsigned buf_size; /*!< the whole buffer size */
84
85 byte_t *begin; /*!< begin of content */
86 byte_t *end; /*!< one-past-end of content */
87 } raw_data_t;
88
89
90 /**
91 * pseudo-alloca raw_data buffer (alloca is not in POSIX)
92 * @param varp ptr-to local raw_data_t variable to which to alloca
93 * @param buf_array array allocated on stack which will be used as storage
94 * (must be auto-variable)
95 * @return alloca'ed memory as raw_data
96 *
97 * Example:
98 *
99 * raw_data_t ack;
100 * byte_t ack_buf[8];
101 *
102 * raw_alloc_onstack(&ack, ack_buf);
103 */
104 #define raw_alloc_onstack(varp, buf_array) do { \
105 (varp)->buf = &(buf_array)[0]; \
106 (varp)->buf_size = sizeof(buf_array); \
107 \
108 (varp)->begin = (varp)->buf; \
109 (varp)->end = (varp)->buf; \
110 } while (0)
111
112
113 /**
114 * xmalloc raw buffer
115 * @param size size in bytes
116 * @return xmalloc'ed memory as raw_data
117 */
raw_xmalloc(size_t size)118 raw_data_t raw_xmalloc(size_t size)
119 {
120 raw_data_t data;
121
122 data.buf = xmalloc(size);
123 data.buf_size = size;
124
125 data.begin = data.buf;
126 data.end = data.buf;
127
128 return data;
129 }
130
131 /**
132 * free raw_data buffer
133 * @param buf raw_data buffer to free
134 */
raw_free(raw_data_t * buf)135 void raw_free(raw_data_t *buf)
136 {
137 free(buf->buf);
138
139 buf->buf = NULL;
140 buf->buf_size = 0;
141 buf->begin = NULL;
142 buf->end = NULL;
143 }
144
145
146 /***************************************************************************/
147
148 /***************
149 * COMLI types *
150 ***************/
151
152 /**
153 * COMLI message header info
154 * @see 1. INTRODUCTION
155 */
156 typedef struct {
157 int id; /*!< Id[1:2] */
158 int stamp; /*!< Stamp[3] */
159 int type; /*!< Mess Type[4] */
160 } msg_head_t;
161
162 /**
163 * COMLI IO header info
164 * @see 1. INTRODUCTION
165 */
166 typedef struct {
167 unsigned addr; /*!< Addr[5:8] */
168 unsigned len; /*!< NOB[9:10] */
169 } io_head_t;
170
171 /**
172 * maximum allowed io.len value
173 */
174 #define IO_LEN_MAX 0xff
175
176 /**
177 * COMLI header info
178 * @see 1. INTRODUCTION
179 */
180 typedef struct {
181 msg_head_t msg; /*!< message header [1:4] */
182 io_head_t io; /*!< io header [5:10] */
183 } comli_head_t;
184
185
186
187 /******************
188 * MISC UTILITIES *
189 ******************/
190
191 /**
192 * convert hex string to int
193 * @param head input string
194 * @param count string length
195 * @return parsed value (>=0) if success, -1 on error
196 */
from_hex(const byte_t * head,unsigned len)197 static long from_hex(const byte_t *head, unsigned len)
198 {
199 long val=0;
200
201 while (len-- != 0) {
202 int ch = *head;
203
204 if (!isxdigit(ch))
205 return -1; /* wrong character */
206
207 val *= 0x10;
208
209 if (isdigit(ch)) {
210 val += (ch-'0');
211 }
212 else {
213 /* ch = toupper(ch) without locale-related problems */
214 if (ch < 'A')
215 ch += 'A' - 'a';
216
217 val += 0x0A + (ch-'A');
218 }
219
220 ++head;
221 }
222
223 return val;
224 }
225
226 /**
227 * compute checksum of a buffer
228 * @see 10. CHECKSUM BCC
229 * @param buf buffer address
230 * @param count no. of bytes in the buffer
231 * @return computed checksum
232 */
compute_bcc(const byte_t * buf,size_t count)233 static byte_t compute_bcc(const byte_t *buf, size_t count)
234 {
235 byte_t bcc=0;
236 unsigned i;
237
238 for (i=0; i<count; ++i)
239 bcc ^= buf[i];
240
241 return bcc;
242 }
243
244
245 /**
246 * reverse bits in a buffer bytes from right to left
247 * @see 6. CODING AND DECODING OF REGISTER VALUES
248 * @param buf buffer address
249 * @param count no. of bytes in the buffer
250 */
reverse_bits(byte_t * buf,size_t count)251 static void reverse_bits(byte_t *buf, size_t count)
252 {
253 byte_t x;
254
255 while (count!=0) {
256 x = *buf;
257 x = ( (x & 0x80) >> 7 ) |
258 ( (x & 0x40) >> 5 ) |
259 ( (x & 0x20) >> 3 ) |
260 ( (x & 0x10) >> 1 ) |
261 ( (x & 0x08) << 1 ) |
262 ( (x & 0x04) << 3 ) |
263 ( (x & 0x02) << 5 ) |
264 ( (x & 0x01) << 7 );
265 *buf = x;
266
267 ++buf;
268 --count;
269 }
270 }
271
272
273 /********************************************************************/
274
275 /*
276 * communication basics
277 *
278 * ME (Monitor Equipment)
279 * PRS (Power Rectifier System) /think of it as of UPS in common speak/
280 *
281 * there are 2 types of transactions:
282 *
283 * 'ACTIVATE COMMAND'
284 * ME -> PRS (al_prep_activate)
285 * ME <- PRS [ack] (al_check_ack)
286 *
287 *
288 * 'READ REGISTER'
289 * ME -> PRS (al_prep_read_req)
290 * ME <- PRS [data] (al_parse_reply)
291 *
292 */
293
294 /********************
295 * COMLI primitives *
296 ********************/
297
298
299 /************************
300 * COMLI: OUTPUT FRAMES *
301 ************************/
302
303 /**
304 * prepare COMLI sentence
305 * @see 1. INTRODUCTION
306 * @param dest [out] where to put the result
307 * @param h COMLI header info
308 * @param buf data part of the sentence
309 * @param count amount of data bytes in the sentence
310 *
311 * @note: the data are copied into the sentence "as-is", there is no conversion is done.
312 * if the caller wants to reverse bits it is necessary to call reverse_bits(...) prior
313 * to comli_prepare.
314 */
comli_prepare(raw_data_t * dest,const comli_head_t * h,const void * buf,size_t count)315 static void comli_prepare(raw_data_t *dest, const comli_head_t *h, const void *buf, size_t count)
316 {
317 /*
318 * 0 1 2 3 4 5 6 7 8 9 10 11 - - - N-1 N
319 * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+
320 * | STX | IDh IDl | Stamp | type | addr1 addr2 addr3 addr4 | NOBh NOBl | ...data... | ETX | BCC |
321 * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+
322 *
323 * ^ ^
324 * | |
325 *begin end
326 */
327 byte_t *out = dest->begin;
328
329
330 /* it's caller responsibility to allocate enough space.
331 else it is a bug in the program */
332 if ( (out+11+count+2) > (dest->buf + dest->buf_size) )
333 fatalx(EXIT_FAILURE, "too small dest in comli_prepare\n");
334
335 out[0] = STX;
336 snprintf((char *)out+1, 10+1, "%02X%1i%1i%04X%02X", h->msg.id, h->msg.stamp, h->msg.type, h->io.addr, h->io.len);
337
338 memcpy(out+11, buf, count);
339 reverse_bits(out+11, count);
340
341
342 out[11+count] = ETX;
343 out[12+count] = compute_bcc(out+1, 10+count+1);
344
345 dest->end = dest->begin + (11+count+2);
346 }
347
348
349
350
351 /**
352 * prepare AL175 read data request
353 * @see 2. MESSAGE TYPE 2 (COMMAND SENT FROM MONITORING EQUIPMENT)
354 * @param dest [out] where to put the result
355 * @param addr start address of requested area
356 * @param count no. of requested bytes
357 */
al_prep_read_req(raw_data_t * dest,unsigned addr,size_t count)358 static void al_prep_read_req(raw_data_t *dest, unsigned addr, size_t count)
359 {
360 comli_head_t h;
361
362 h.msg.id = 0x14;
363 h.msg.stamp = 1;
364 h.msg.type = 2;
365
366 h.io.addr = addr;
367 h.io.len = count;
368
369 comli_prepare(dest, &h, NULL, 0);
370 }
371
372
373 /**
374 * prepare AL175 activate command
375 * @see 4. MESSAGE TYPE 0 (ACTIVATE COMMAND)
376 * @param dest [out] where to put the result
377 * @param cmd command type [11]
378 * @param subcmd command subtype [12]
379 * @param pr1 first parameter [13:14]
380 * @param pr2 second parameter [15:16]
381 * @param pr3 third parameter [17:18]
382 */
al_prep_activate(raw_data_t * dest,byte_t cmd,byte_t subcmd,uint16_t pr1,uint16_t pr2,uint16_t pr3)383 static void al_prep_activate(raw_data_t *dest, byte_t cmd, byte_t subcmd, uint16_t pr1, uint16_t pr2, uint16_t pr3)
384 {
385 comli_head_t h;
386 char data[8+1];
387
388 h.msg.id = 0x14;
389 h.msg.stamp = 1;
390 h.msg.type = 0;
391
392 h.io.addr = 0x4500;
393 h.io.len = 8;
394
395 /* NOTE: doc says we should use ASCII coding here, but the actual
396 * values are > 0x80, so we use binary coding */
397 data[0] = cmd;
398 data[1] = subcmd;
399
400 snprintf(data+2, 6+1, "%2X%2X%2X", pr1, pr2, pr3);
401
402 comli_prepare(dest, &h, data, 8);
403 }
404
405 /***********************
406 * COMLI: INPUT FRAMES *
407 ***********************/
408
409 /**
410 * check COMLI frame for correct layout and bcc
411 * @param f frame to check
412 *
413 * @return 0 (ok) -1 (error)
414 */
comli_check_frame(raw_data_t f)415 static int comli_check_frame(/*const*/ raw_data_t f)
416 {
417 int bcc;
418 byte_t *tail;
419
420 if (*f.begin!=STX)
421 return -1;
422
423 tail = f.end - 2;
424 if (tail <= f.begin)
425 return -1;
426
427 if (tail[0]!=ETX)
428 return -1;
429
430 bcc = compute_bcc(f.begin+1, (f.end - f.begin) - 2/*STX & BCC*/);
431 if (bcc!= tail[1])
432 return -1;
433
434 return 0;
435 }
436
437
438 /**
439 * parse reply header from PRS
440 * @see 3. MESSAGE TYPE 0 (REPLY FROM PRS ON MESSAGE TYPE 2)
441 *
442 * @param io [out] parsed io_header
443 * @param raw_reply_head [in] raw reply header from PRS
444 * @return 0 (ok), -1 (error)
445 *
446 * @see al_parse_reply
447 */
al_parse_reply_head(io_head_t * io,const raw_data_t raw_reply_head)448 static int al_parse_reply_head(io_head_t *io, const raw_data_t raw_reply_head)
449 {
450 /*
451 * 0 1 2 3 4 5 6 7 8 9 10
452 * +-----+---------+-------+------+-------------------------+-----------+-----------+
453 * | STX | IDh IDl | Stamp | type | addr1 addr2 addr3 addr4 | NOBh NOBl | ......... |
454 * +-----+---------+-------+------+-------------------------+-----------+-----------+
455 *
456 * ^ ^
457 * | |
458 * begin end
459 */
460
461 unsigned long io_addr, io_len;
462 const byte_t *reply_head = raw_reply_head.begin - 1;
463
464 if ( (raw_reply_head.end - raw_reply_head.begin) != 10) {
465 upsdebugx(3, "%s: wrong size\t(%i != 10)", __func__, (int)(raw_reply_head.end - raw_reply_head.begin));
466 return -1; /* wrong size */
467 }
468
469 if (reply_head[1]!='0' || reply_head[2]!='0') {
470 upsdebugx(3, "%s: wrong id\t('%c%c' != '00')", __func__, reply_head[1], reply_head[2]);
471 return -1; /* wrong id */
472 }
473
474 if (reply_head[3]!='1') {
475 upsdebugx(3, "%s: wrong stamp\t('%c' != '1')", __func__, reply_head[3]);
476 return -1; /* wrong stamp */
477 }
478
479 if (reply_head[4]!='0') {
480 upsdebugx(3, "%s: wrong type\t('%c' != '0')", __func__, reply_head[4]);
481 return -1; /* wrong type */
482 }
483
484 io_addr = from_hex(&reply_head[5], 4);
485 if (io_addr==-1UL) {
486 upsdebugx(3, "%s: invalid addr\t('%c%c%c%c')", __func__, reply_head[5],reply_head[6],reply_head[7],reply_head[8]);
487 return -1; /* wrong addr */
488 }
489
490 io_len = from_hex(&reply_head[9], 2);
491 if (io_len==-1UL) {
492 upsdebugx(3, "%s: invalid nob\t('%c%c')", __func__, reply_head[9],reply_head[10]);
493 return -1; /* wrong NOB */
494 }
495
496 if (io_len > IO_LEN_MAX) {
497 upsdebugx(3, "nob too big\t(%lu > %i)", io_len, IO_LEN_MAX);
498 return -1; /* too much data claimed */
499 }
500
501 io->addr = io_addr;
502 io->len = io_len;
503
504
505 return 0;
506 }
507
508
509 /**
510 * parse reply from PRS
511 * @see 3. MESSAGE TYPE 0 (REPLY FROM PRS ON MESSAGE TYPE 2)
512 * @param io_head [out] parsed io_header
513 * @param io_buf [in] [out] raw_data where to place incoming data (see ...data... below)
514 * @param raw_reply raw reply from PRS to check
515 * @return 0 (ok), -1 (error)
516 *
517 * @see al_parse_reply_head
518 */
al_parse_reply(io_head_t * io_head,raw_data_t * io_buf,raw_data_t raw_reply)519 static int al_parse_reply(io_head_t *io_head, raw_data_t *io_buf, /*const*/ raw_data_t raw_reply)
520 {
521 /*
522 * 0 1 2 3 4 5 6 7 8 9 10 11 - - - N-1 N
523 * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+
524 * | STX | IDh IDl | Stamp | type | addr1 addr2 addr3 addr4 | NOBh NOBl | ...data... | ETX | BCC |
525 * +-----+---------+-------+------+-------------------------+-----------+------------+-----+-----+
526 *
527 * ^ ^
528 * | |
529 * begin end
530 */
531
532 int err;
533 unsigned i;
534 const byte_t *reply = raw_reply.begin - 1;
535
536 /* 1: extract header and parse it */
537 /*const*/ raw_data_t raw_reply_head = raw_reply;
538
539 if (raw_reply_head.begin + 10 <= raw_reply_head.end)
540 raw_reply_head.end = raw_reply_head.begin + 10;
541
542 err = al_parse_reply_head(io_head, raw_reply_head);
543 if (err==-1)
544 return -1;
545
546
547 /* 2: process data */
548 reply = raw_reply.begin - 1;
549
550 if ( (raw_reply.end - raw_reply.begin) != (ptrdiff_t)(10 + io_head->len)) {
551 upsdebugx(3, "%s: corrupt sentence\t(%i != %i)",
552 __func__, (int)(raw_reply.end - raw_reply.begin), 10 + io_head->len);
553 return -1; /* corrupt sentence */
554 }
555
556
557 /* extract the data */
558 if (io_buf->buf_size < io_head->len) {
559 upsdebugx(3, "%s: too much data to fit in io_buf\t(%u > %u)",
560 __func__, io_head->len, io_buf->buf_size);
561 return -1; /* too much data to fit in io_buf */
562 }
563
564 io_buf->begin = io_buf->buf;
565 io_buf->end = io_buf->begin;
566
567 for (i=0; i<io_head->len; ++i)
568 *(io_buf->end++) = reply[11+i];
569
570 reverse_bits(io_buf->begin, (io_buf->end - io_buf->begin) );
571
572 upsdebug_hex(3, "\t\t--> payload", io_buf->begin, (io_buf->end - io_buf->begin));
573
574 return 0; /* all ok */
575 }
576
577
578 /**
579 * check acknowledge from PRS
580 * @see 5. ACKNOWLEDGE FROM PRS
581 * @param raw_ack raw acknowledge from PRS to check
582 * @return 0 on success, -1 on error
583 */
al_check_ack(raw_data_t raw_ack)584 static int al_check_ack(/*const*/ raw_data_t raw_ack)
585 {
586 /*
587 * 0 1 2 3 4 5 6 7
588 * +-----+---------+-------+------+-----+-----+-----+
589 * | STX | IDh IDl | Stamp | type | ACK | ETX | BCC |
590 * +-----+---------+-------+------+-----+-----+-----+
591 *
592 * ^ ^
593 * | |
594 * begin end
595 */
596
597 const byte_t *ack = raw_ack.begin - 1;
598
599 if ( (raw_ack.end - raw_ack.begin) !=5) {
600 upsdebugx(3, "%s: wrong size\t(%i != 5)", __func__, (int)(raw_ack.end - raw_ack.begin));
601 return -1; /* wrong size */
602 }
603
604 if (ack[1]!='0' || ack[2]!='0') {
605 upsdebugx(3, "%s: wrong id\t('%c%c' != '00')", __func__, ack[1], ack[2]);
606 return -1; /* wrong id */
607 }
608
609 /* the following in not mandated. it is just said it will be
610 * "same as one received". but we always send '1' (0x31) as stamp
611 * (see 4. MESSAGE TYPE 0 (ACTIVATE COMMAND). Hence, stamp checking
612 * is hardcoded here.
613 */
614 if (ack[3]!='1') {
615 upsdebugx(3, "%s: wrong stamp\t('%c' != '1')", __func__, ack[3]);
616 return -1; /* wrong stamp */
617 }
618
619 if (ack[4]!='1') {
620 upsdebugx(3, "%s: wrong type\t('%c' != '1')", __func__, ack[4]);
621 return -1; /* wrong type */
622 }
623
624 if (ack[5]!=ACK) {
625 upsdebugx(3, "%s: wrong ack\t(0x%02X != 0x%02X)", __func__, ack[5], ACK);
626 return -1; /* wrong ack */
627 }
628
629
630 return 0;
631 }
632
633
634
635
636
637 /******************************************************************/
638
639
640 /**********
641 * SERIAL *
642 **********/
643
644 /* clear any flow control (copy from powercom.c) */
ser_disable_flow_control(void)645 static void ser_disable_flow_control (void)
646 {
647 struct termios tio;
648
649 tcgetattr (upsfd, &tio);
650
651 tio.c_iflag &= ~ (IXON | IXOFF);
652 tio.c_cc[VSTART] = _POSIX_VDISABLE;
653 tio.c_cc[VSTOP] = _POSIX_VDISABLE;
654
655 upsdebugx(4, "Flow control disable");
656
657 /* disable any flow control */
658 tcsetattr(upsfd, TCSANOW, &tio);
659 }
660
flush_rx_queue()661 static void flush_rx_queue()
662 {
663 ser_flush_in(upsfd, "", /*verbose=*/nut_debug_level);
664 }
665
666 /**
667 * transmit frame to PRS
668 *
669 * @param dmsg debug message prefix
670 * @param frame the frame to tansmit
671 * @return 0 (ok) -1 (error)
672 */
tx(const char * dmsg,raw_data_t frame)673 static int tx(const char *dmsg, /*const*/ raw_data_t frame)
674 {
675 int err;
676
677 upsdebug_ascii(3, dmsg, frame.begin, (frame.end - frame.begin));
678
679 err = ser_send_buf(upsfd, frame.begin, (frame.end - frame.begin) );
680 if (err==-1) {
681 upslogx(LOG_ERR, "failed to send frame to PRS: %s", strerror(errno));
682 return -1;
683 }
684
685 if (err != (frame.end - frame.begin)) {
686 upslogx(LOG_ERR, "sent incomplete frame to PRS");
687 return -1;
688 }
689
690 return 0;
691 }
692
693 /***********
694 * CHATTER *
695 ***********/
696
697 static time_t T_io_begin; /* start of current I/O transaction */
698 static int T_io_timeout; /* in seconds */
699
700 /* start new I/O transaction with maximum time limit */
io_new_transaction(int timeout)701 static void io_new_transaction(int timeout)
702 {
703 T_io_begin = time(NULL);
704 T_io_timeout = timeout;
705 }
706
707 /**
708 * get next character from input stream
709 *
710 * @param ch ptr-to where store result
711 *
712 * @return -1 (error) 0 (timeout) >0 (got it)
713 *
714 */
get_char(char * ch)715 static int get_char(char *ch)
716 {
717 time_t now = time(NULL);
718 long rx_timeout;
719
720 rx_timeout = T_io_timeout - (now - T_io_begin);
721 /* negative rx_timeout -> time already out */
722 if (rx_timeout < 0)
723 return 0;
724 return ser_get_char(upsfd, ch, rx_timeout, 0);
725 }
726
727
728 /**
729 * get next characters from input stream
730 *
731 * @param buf ptr-to output buffer
732 * @param len buffer length
733 *
734 * @return -1 (error) 0 (timeout) >0 (no. of characters actually read)
735 *
736 */
get_buf(byte_t * buf,size_t len)737 static int get_buf(byte_t *buf, size_t len)
738 {
739 time_t now = time(NULL);
740 long rx_timeout;
741
742 rx_timeout = T_io_timeout - (now - T_io_begin);
743 /* negative rx_timeout -> time already out */
744 if (rx_timeout < 0)
745 return 0;
746 return ser_get_buf_len(upsfd, buf, len, rx_timeout, 0);
747 }
748
749 /**
750 * scan incoming bytes for specific character
751 *
752 * @return 0 (got it) -1 (error)
753 */
scan_for(char c)754 static int scan_for(char c)
755 {
756 char in;
757 int err;
758
759 while (1) {
760 err = get_char(&in);
761 if (err==-1 || err==0 /*timeout*/)
762 return -1;
763
764 if (in==c)
765 break;
766 }
767
768 return 0;
769 }
770
771
772 /**
773 * receive 'activate command' ACK from PRS
774 *
775 * @return 0 (ok) -1 (error)
776 */
recv_command_ack()777 static int recv_command_ack()
778 {
779 int err;
780 raw_data_t ack;
781 byte_t ack_buf[8];
782
783 /* 1: STX */
784 err = scan_for(STX);
785 if (err==-1)
786 return -1;
787
788
789 raw_alloc_onstack(&ack, ack_buf);
790 *(ack.end++) = STX;
791
792
793 /* 2: ID1 ID2 STAMP MSG_TYPE ACK ETX BCC */
794 err = get_buf(ack.end, 7);
795 if (err!=7)
796 return -1;
797
798 ack.end += 7;
799
800 /* frame constructed - let's verify it */
801 upsdebug_ascii(3, "rx (ack):\t\t", ack.begin, (ack.end - ack.begin));
802
803 /* generic layout */
804 err = comli_check_frame(ack);
805 if (err==-1)
806 return -1;
807
808 /* shrink frame */
809 ack.begin += 1;
810 ack.end -= 2;
811
812 return al_check_ack(ack);
813 }
814
815 /**
816 * receive 'read register' data from PRS
817 * @param io [out] io header of received data
818 * @param io_buf [in] [out] where to place incoming data
819 *
820 * @return 0 (ok) -1 (error)
821 */
recv_register_data(io_head_t * io,raw_data_t * io_buf)822 static int recv_register_data(io_head_t *io, raw_data_t *io_buf)
823 {
824 int err, ret;
825 raw_data_t reply_head;
826 raw_data_t reply;
827
828 byte_t reply_head_buf[11];
829
830 /* 1: STX */
831 err = scan_for(STX);
832 if (err==-1)
833 return -1;
834
835 raw_alloc_onstack(&reply_head, reply_head_buf);
836 *(reply_head.end++) = STX;
837
838
839 /* 2: ID1 ID2 STAMP MSG_TYPE ADDR1 ADDR2 ADDR3 ADDR4 LEN1 LEN2 */
840 err = get_buf(reply_head.end, 10);
841 if (err!=10)
842 return -1;
843
844 reply_head.end += 10;
845
846 upsdebug_ascii(3, "rx (head):\t", reply_head.begin, (reply_head.end - reply_head.begin));
847
848
849 /* 3: check header, extract IO info */
850 reply_head.begin += 1; /* temporarily strip STX */
851
852 err = al_parse_reply_head(io, reply_head);
853 if (err==-1)
854 return -1;
855
856 reply_head.begin -= 1; /* restore STX */
857
858 upsdebugx(4, "\t\t--> addr: 0x%x len: 0x%x", io->addr, io->len);
859
860 /* 4: allocate space for full reply and copy header there */
861 reply = raw_xmalloc(11/*head*/ + io->len/*data*/ + 2/*ETX BCC*/);
862
863 memcpy(reply.end, reply_head.begin, (reply_head.end - reply_head.begin));
864 reply.end += (reply_head.end - reply_head.begin);
865
866 /* 5: receive tail of the frame */
867 err = get_buf(reply.end, io->len + 2);
868 if (err!=(int)(io->len+2)) {
869 upsdebugx(4, "rx_tail failed, err=%i (!= %i)", err, io->len+2);
870 ret = -1; goto out;
871 }
872
873 reply.end += io->len + 2;
874
875
876 /* frame constructed, let's verify it */
877 upsdebug_ascii(3, "rx (head+data):\t", reply.begin, (reply.end - reply.begin));
878
879 /* generic layout */
880 err = comli_check_frame(reply);
881 if (err==-1) {
882 upsdebugx(3, "%s: corrupt frame", __func__);
883 ret = -1; goto out;
884 }
885
886 /* shrink frame */
887 reply.begin += 1;
888 reply.end -= 2;
889
890
891 /* XXX: a bit of processing duplication here */
892 ret = al_parse_reply(io, io_buf, reply);
893
894 out:
895 raw_free(&reply);
896 return ret;
897 }
898
899
900 /*****************************************************************/
901
902 /*********************
903 * AL175: DO COMMAND *
904 *********************/
905
906 /**
907 * do 'ACTIVATE COMMAND'
908 *
909 * @return 0 (ok) -1 (error)
910 */
al175_do(byte_t cmd,byte_t subcmd,uint16_t pr1,uint16_t pr2,uint16_t pr3)911 static int al175_do(byte_t cmd, byte_t subcmd, uint16_t pr1, uint16_t pr2, uint16_t pr3)
912 {
913 int err;
914 raw_data_t CTRL_frame;
915 byte_t CTRL_frame_buf[512];
916
917 raw_alloc_onstack(&CTRL_frame, CTRL_frame_buf);
918 al_prep_activate(&CTRL_frame, cmd, subcmd, pr1, pr2, pr3);
919
920 flush_rx_queue(); /* DROP */
921
922 err = tx("tx (ctrl):\t", CTRL_frame); /* TX */
923 if (err==-1)
924 return -1;
925
926
927 return recv_command_ack(); /* RX */
928 }
929
930
931 /**
932 * 'READ REGISTER'
933 *
934 */
al175_read(byte_t * dst,unsigned addr,size_t count)935 static int al175_read(byte_t *dst, unsigned addr, size_t count)
936 {
937 int err;
938 raw_data_t REQ_frame;
939 raw_data_t rx_data;
940 io_head_t io;
941
942 byte_t REQ_frame_buf[512];
943
944 raw_alloc_onstack(&REQ_frame, REQ_frame_buf);
945 al_prep_read_req(&REQ_frame, addr, count);
946
947 flush_rx_queue(); /* DROP */
948
949 err = tx("tx (req):\t", REQ_frame); /* TX */
950 if (err==-1)
951 return -1;
952
953
954 rx_data.buf = dst;
955 rx_data.buf_size = count;
956 rx_data.begin = dst;
957 rx_data.end = dst;
958
959 err = recv_register_data(&io, &rx_data);
960 if (err==-1)
961 return -1;
962
963 if ((rx_data.end - rx_data.begin) != (int)count)
964 return -1;
965
966 if ( (io.addr != addr) || (io.len != count) ) {
967 upsdebugx(3, "%s: io_head mismatch\t(%x,%x != %x,%x)",
968 __func__, io.addr, io.len, addr,
969 (unsigned int)count);
970 return -1;
971 }
972
973
974 return 0;
975 }
976
977 /*************
978 * NUT STUFF *
979 *************/
980
981 /****************************
982 * ACTIVATE COMMANDS table
983 *
984 * see 8. ACTIVATE COMMANDS
985 */
986
987 typedef int mm_t; /* minutes */
988 typedef int VV_t; /* voltage */
989
990 #define Z1 , 0
991 #define Z2 , 0, 0
992 #define Z3 , 0, 0, 0
993
994 #define ACT int
995
TOGGLE_PRS_ONOFF()996 ACT TOGGLE_PRS_ONOFF () { return al175_do(0x81, 0x80 Z3); }
CANCEL_BOOST()997 ACT CANCEL_BOOST () { return al175_do(0x82, 0x80 Z3); }
STOP_BATTERY_TEST()998 ACT STOP_BATTERY_TEST () { return al175_do(0x83, 0x80 Z3); }
START_BATTERY_TEST(VV_t EndVolt,unsigned Minutes)999 ACT START_BATTERY_TEST (VV_t EndVolt, unsigned Minutes)
1000 { return al175_do(0x83, 0x81, EndVolt, Minutes Z1); }
1001
SET_FLOAT_VOLTAGE(VV_t v)1002 ACT SET_FLOAT_VOLTAGE (VV_t v) { return al175_do(0x87, 0x80, v Z2); }
SET_BOOST_VOLTAGE(VV_t v)1003 ACT SET_BOOST_VOLTAGE (VV_t v) { return al175_do(0x87, 0x81, v Z2); }
SET_HIGH_BATTERY_LIMIT(VV_t Vhigh)1004 ACT SET_HIGH_BATTERY_LIMIT (VV_t Vhigh) { return al175_do(0x87, 0x82, Vhigh Z2); }
SET_LOW_BATTERY_LIMIT(VV_t Vlow)1005 ACT SET_LOW_BATTERY_LIMIT (VV_t Vlow) { return al175_do(0x87, 0x83, Vlow Z2); }
1006
SET_DISCONNECT_LEVEL_AND_DELAY(VV_t level,mm_t delay)1007 ACT SET_DISCONNECT_LEVEL_AND_DELAY
1008 (VV_t level, mm_t delay)
1009 { return al175_do(0x87, 0x84, level, delay Z1); }
1010
RESET_ALARMS()1011 ACT RESET_ALARMS () { return al175_do(0x88, 0x80 Z3); }
CHANGE_COMM_PROTOCOL()1012 ACT CHANGE_COMM_PROTOCOL () { return al175_do(0x89, 0x80 Z3); }
SET_VOLTAGE_AT_ZERO_T(VV_t v)1013 ACT SET_VOLTAGE_AT_ZERO_T (VV_t v) { return al175_do(0x8a, 0x80, v Z2); }
SET_SLOPE_AT_ZERO_T(VV_t mv_per_degree)1014 ACT SET_SLOPE_AT_ZERO_T (VV_t mv_per_degree)
1015 { return al175_do(0x8a, 0x81, mv_per_degree Z2); }
1016
SET_MAX_TCOMP_VOLTAGE(VV_t v)1017 ACT SET_MAX_TCOMP_VOLTAGE (VV_t v) { return al175_do(0x8a, 0x82, v Z2); }
SET_MIN_TCOMP_VOLTAGE(VV_t v)1018 ACT SET_MIN_TCOMP_VOLTAGE (VV_t v) { return al175_do(0x8a, 0x83, v Z2); }
SWITCH_TEMP_COMP(int on)1019 ACT SWITCH_TEMP_COMP (int on) { return al175_do(0x8b, 0x80, on Z2); }
1020
SWITCH_SYM_ALARM()1021 ACT SWITCH_SYM_ALARM () { return al175_do(0x8c, 0x80 Z3); }
1022
1023
1024 /**
1025 * extract double value from a word
1026 */
d16(byte_t data[2])1027 static double d16(byte_t data[2])
1028 {
1029 return (data[1] + 0x100*data[0]) / 100.0;
1030 }
1031
upsdrv_updateinfo(void)1032 void upsdrv_updateinfo(void)
1033 {
1034 /* int flags; */
1035
1036 byte_t x4000[9]; /* registers from 0x4000 to 0x4040 inclusive */
1037 byte_t x4048[2]; /* 0x4048 - 0x4050 */
1038 byte_t x4100[8]; /* 0x4100 - 0x4138 */
1039 byte_t x4180[8]; /* 0x4180 - 0x41b8 */
1040 byte_t x4300[2]; /* 0x4300 - 0x4308 */
1041 int err;
1042
1043 double batt_current = 0.0;
1044
1045
1046 upsdebugx(4, " ");
1047 upsdebugx(4, "UPDATEINFO");
1048 upsdebugx(4, "----------");
1049 io_new_transaction(/*timeout=*/3);
1050
1051 #define RECV(reg) do { \
1052 err = al175_read(x ## reg, 0x ## reg, sizeof(x ## reg)); \
1053 if (err==-1) { \
1054 dstate_datastale(); \
1055 return; \
1056 } \
1057 } while (0)
1058
1059 RECV(4000);
1060 RECV(4048);
1061 RECV(4100);
1062 RECV(4180);
1063 RECV(4300);
1064
1065
1066 status_init();
1067
1068 /* XXX non conformant with NUT naming & not well understood what they mean */
1069 #if 0
1070 /* 0x4000 DIGITAL INPUT 1-8 */
1071 dstate_setinfo("load.fuse", (x4000[0] & 0x80) ? "OK" : "BLOWN");
1072 dstate_setinfo("battery.fuse", (x4000[0] & 0x40) ? "OK" : "BLOWN");
1073 dstate_setinfo("symalarm.fuse", (x4000[0] & 0x20) ? "OK" : "BLOWN");
1074
1075 /* 0x4008 BATTERY INFORMATION */
1076 dstate_setinfo("battery.contactor", (x4000[1] & 0x80) ? "XX" : "YY"); /* FIXME */
1077 dstate_setinfo("load.contactor", (x4000[1] & 0x40) ? "XX" : "YY"); /* FIXME */
1078 dstate_setinfo("lvd.contactor", (x4000[1] & 0x20) ? "XX" : "YY"); /* FIXME */
1079 #endif
1080 if (x4000[0] & 0x40){
1081 dstate_setinfo("battery.fuse", "FAIL");
1082 status_set("RB");
1083 }else{
1084 dstate_setinfo("battery.fuse", "OK");
1085 }
1086
1087 if (x4000[0] & 0x20){
1088 dstate_setinfo("battery.symmetry", "FAIL");
1089 status_set("RB");
1090 }else{
1091 dstate_setinfo("battery.symmetry", "OK");
1092 }
1093
1094 if (x4000[1] & 0x01) /* battery test running */
1095 status_set("TEST");
1096
1097 /* TODO: others from 0x4008 */
1098
1099 /* 0x4010 NOT USED */
1100 /* 0x4018 NOT USED */
1101
1102 switch (x4000[4]) { /* 0x4020 MAINS VOLTAGE STATUS */
1103 case 0: status_set("OL"); break;
1104 case 1: status_set("OB"); break;
1105
1106 case 2: /* doc: "not applicable" */
1107 default:
1108 upsdebugx(2, "%s: invalid mains voltage status\t(%i)", __func__, x4000[4]);
1109 }
1110
1111 /* 0x4028 SYSTEM ON OFF STATUS */
1112 switch (x4000[5]) {
1113 case 0: /* system on */ break;
1114 case 1: status_set("OFF"); break;
1115
1116 default:
1117 upsdebugx(2, "%s: invalid system on/off status\t(%i)", __func__, x4000[5]);
1118 }
1119
1120 switch (x4000[6]) { /* 0x4030 BATTERY TEST FAIL */
1121 case 0: dstate_setinfo("ups.test.result", "OK");
1122 break;
1123
1124 case 1: status_set("RB");
1125 dstate_setinfo("ups.test.result", "FAIL");
1126 break;
1127
1128 default:
1129 upsdebugx(2, "%s: invalid battery test fail\t(%i)", __func__, x4000[6]);
1130 }
1131 switch (x4000[7]) { /* 0x4038 BATTERY VOLTAGE STATUS */
1132 case 0: /* normal */ break;
1133 case 1: status_set("LB"); break;
1134 case 2: status_set("HB"); break;
1135
1136 default:
1137 upsdebugx(2, "%s: invalid battery voltage status\t(%i)", __func__, x4000[7]);
1138 }
1139 switch (x4000[8]) { /* 0x4040 POS./NEG. BATT. CURRENT */
1140 case 0: batt_current = +1.0; break; /* positive */
1141 case 1: batt_current = -1.0; break; /* negative */
1142
1143 default:
1144 upsdebugx(2, "%s: invalid pos/neg battery current\t(%i)", __func__, x4000[8]);
1145 }
1146
1147 switch (x4048[0]) { /* 0x4048 BOOST STATUS */
1148 case 0: /* no boost */; break;
1149 case 1: status_set("BOOST"); break;
1150
1151 default:
1152 upsdebugx(2, "%s: invalid boost status\t(%i)", __func__, x4048[0]);
1153 }
1154
1155 {
1156 const char *v=NULL;
1157
1158 switch (x4048[1]) { /* 0x4050 SYSTEM VOLTAGE STAT. */
1159 case 0: v = "48"; break;
1160 case 1: v = "24"; break;
1161 case 2: v = "12"; break;
1162 case 3: v = "26"; break;
1163 case 4: v = "60"; break;
1164
1165 default:
1166 upsdebugx(2, "%s: invalid system voltage status\t(%i)", __func__, x4048[1]);
1167 }
1168
1169 if (v)
1170 dstate_setinfo("output.voltage.nominal", "%s", v);
1171 }
1172
1173
1174 /* 0x4100 BATTERY VOLTAGE REF */
1175 dstate_setinfo("battery.voltage.nominal", "%.2f", d16(x4100+0));
1176
1177 /* 0x4110 BOOST VOLTAGE REF */
1178 dstate_setinfo("input.transfer.boost.low", "%.2f", d16(x4100+2)); /* XXX: boost.high ? */
1179
1180 /* 0x4120 HIGH BATT VOLT REF XXX */
1181 /* 0x4130 LOW BATT VOLT REF XXX */
1182
1183 /* 0x4180 FLOAT VOLTAGE XXX */
1184 /* 0x4190 BATT CURRENT */
1185 batt_current *= d16(x4180+2);
1186 dstate_setinfo("battery.current", "%.2f", batt_current);
1187
1188 /* 0x41b0 LOAD CURRENT (output.current in NUT) */
1189 dstate_setinfo("output.current", "%.2f", d16(x4180+6));
1190
1191 /* 0x4300 BATTERY TEMPERATURE */
1192 dstate_setinfo("battery.temperature", "%.2f", d16(x4300+0));
1193
1194
1195 status_commit();
1196
1197 upsdebugx(1, "STATUS: %s", dstate_getinfo("ups.status"));
1198 dstate_dataok();
1199
1200
1201 /* out: */
1202 return;
1203
1204 }
1205
upsdrv_shutdown(void)1206 void upsdrv_shutdown(void)
1207 {
1208 /* TODO use TOGGLE_PRS_ONOFF for shutdown */
1209
1210 /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
1211
1212 /* maybe try to detect the UPS here, but try a shutdown even if
1213 it doesn't respond at first if possible */
1214
1215 /* replace with a proper shutdown function */
1216 fatalx(EXIT_FAILURE, "shutdown not supported");
1217
1218 /* you may have to check the line status since the commands
1219 for toggling power are frequently different for OL vs. OB */
1220
1221 /* OL: this must power cycle the load if possible */
1222
1223 /* OB: the load must remain off until the power returns */
1224 }
1225
1226
instcmd(const char * cmdname,const char * extra)1227 static int instcmd(const char *cmdname, const char *extra)
1228 {
1229 int err;
1230
1231 upsdebugx(1, "INSTCMD: %s", cmdname);
1232
1233 io_new_transaction(/*timeout=*/5);
1234
1235 /*
1236 * test.battery.start
1237 * test.battery.stop
1238 */
1239
1240 if (!strcasecmp(cmdname, "test.battery.start")) {
1241 err = START_BATTERY_TEST(24, 1);
1242 return (!err ? STAT_INSTCMD_HANDLED : STAT_INSTCMD_FAILED);
1243 }
1244
1245 if (!strcasecmp(cmdname, "test.battery.stop")) {
1246 err = STOP_BATTERY_TEST();
1247 return (!err ? STAT_INSTCMD_HANDLED : STAT_INSTCMD_FAILED);
1248 }
1249
1250 upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
1251 return STAT_INSTCMD_UNKNOWN;
1252 }
1253
1254 /* no help */
upsdrv_help(void)1255 void upsdrv_help(void)
1256 {
1257 }
1258
1259 /* no -x flags */
upsdrv_makevartable(void)1260 void upsdrv_makevartable(void)
1261 {
1262 }
1263
upsdrv_initups(void)1264 void upsdrv_initups(void)
1265 {
1266 upsfd = ser_open(device_path);
1267 ser_set_speed(upsfd, device_path, B9600);
1268
1269 ser_disable_flow_control();
1270 }
1271
upsdrv_cleanup(void)1272 void upsdrv_cleanup(void)
1273 {
1274 ser_close(upsfd, device_path);
1275 }
1276
1277
upsdrv_initinfo(void)1278 void upsdrv_initinfo(void)
1279 {
1280 /* TODO issue short io with UPS to detect it's presence */
1281 /* try to detect the UPS here - call fatal_with_errno(EXIT_FAILURE, ) if it fails */
1282
1283 dstate_setinfo("ups.mfr", "Eltek");
1284 dstate_setinfo("ups.model", "AL175");
1285 /* ... */
1286
1287 /* instant commands */
1288 dstate_addcmd ("test.battery.start");
1289 dstate_addcmd ("test.battery.stop");
1290 /* TODO rest instcmd(s) */
1291
1292 upsh.instcmd = instcmd;
1293 }
1294