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