[Nut-upsdev] [PATCH] al175: updated driver, please restore it

Kirill Smelkov kirr at mns.spb.ru
Fri Dec 26 09:31:02 UTC 2008


Hi Arnaud,

> Hi Kirill,
>
> just to notify you that your al175 driver is being removed from the
> NUT tree, as of 2.4.0-pre1.
>
> if you wish to see it entering the tree again, please contact the
> Development mailing list to talk about it.

I wish it very much, that's why I'm here.

Yes, back in 2005 I was young and idealistic, that's why you finally marked
al175 as 'broken', but now I understand your points and that in NUT you need
good portability.

So this time I've checked everything with

	CC="gcc -std=c89 -pedantic", and
	CC="gcc -std=c99 -pedantic"

Though I'd like to, but could not use -pedantic-errors, becuase there is a
portablilty problem in drivers/main.h:

	main.h|60| error: ISO C90 does not support flexible array members

----

Changes since al175 as recently removed from NUT tree:

- DEBUG macro replaced with runtime debugging switch (using nut_debug_level)
- rechecked http://eu1.networkupstools.org/doc/2.2.0/developers.html and
  applied where apporpiate

Also

> 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.

Yes, at present shutdown is not supported, and unfortunately now I don't have
AL175 hardware at hand, so that I can't write it and verify the implementation.

I've marked the driver as DRV_EXPERIMENTAL, although it was tested by us as
part of our systems to work OK for more than three years in production
environment on ships (and we don't need shutdown there -- in critical
situations the system has to operate as long as possible, untill the battery is
empty)


Also, all of the previous issues listed below are now fixed in this al175
version:


- ‘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)


Yes, "All the world is not an x86 Linux box," and I've tried to make all the
world happy.

Please apply.

> Merry Christmas and happy New Year.
> Arnaud

Merry Christmas and happy New Year to you too!

Kirill.

 MAINTAINERS         |    4 +
 data/driver.list    |    2 +
 docs/al175-vars.txt |   28 +
 drivers/Makefile.am |    3 +-
 drivers/al175.c     | 1372 +++++++++++++++++++++++++++++++++++++++++++++++++++
 man/Makefile.am     |    1 +
 man/al175.8         |   53 ++
 man/nutupsdrv.8     |    2 +-
 8 files changed, 1463 insertions(+), 2 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index c8c4e97..6c14603 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -53,6 +53,10 @@ P: Peter Selinger
 M: selinger at users.sourceforge.net
 S: Maintained: usbhid-ups belkinunv
 
+P: Kirill Smelkov
+M: kirr at mns.spb.ru
+S: Maintained: al175
+
 Packaging
 =========
 
diff --git a/data/driver.list b/data/driver.list
index 3b1f3f3..8950873 100644
--- a/data/driver.list
+++ b/data/driver.list
@@ -181,6 +181,8 @@
 "Effekta"	"MI/MT/MH"	"2502 cable"	"megatec"
 "Effekta"	"RM2000MH"	""	"megatec"
 
+"Eltek"		"any with AL175 alarm module"	""	"al175"
+
 "Energy Sistem"	"(various)"	""	"megatec"
 
 "ETA"		"mini+UPS"	"WinNT/Upsoft cable"	"genericups upstype=7"
diff --git a/docs/al175-vars.txt b/docs/al175-vars.txt
new file mode 100644
index 0000000..494f9da
--- /dev/null
+++ b/docs/al175-vars.txt
@@ -0,0 +1,28 @@
+Desc: Variables supported by al175 driver
+File: al175-vars.txt
+Date: 02 September 2005
+Auth: Kirill Smelkov <kirr at mns.spb.ru>
+
+* this is a draft *
+
+load.fuse
+battery.fuse        FAIL if fuse on battery (set status.RB flag)
+battery.symmetric   set status.RB flag
+
+battery.contactor
+load.contactor
+lvd.contactor
+
+
+
+STATUS = OL OB OFF RB LB HB TEST BOOST
+
+ups.test.result
+
+
+output.voltage.nominal
+
+battery.voltage.nominal
+input.transfer.boost.low
+
+battery.current
diff --git a/drivers/Makefile.am b/drivers/Makefile.am
index 952b4ba..fa2679e 100644
--- a/drivers/Makefile.am
+++ b/drivers/Makefile.am
@@ -34,7 +34,7 @@ if WITH_LIBPOWERMAN
   AM_CFLAGS += $(LIBPOWERMAN_CFLAGS)
 endif
 
-SERIAL_DRIVERLIST = apcsmart bcmxcp belkin belkinunv bestfcom	\
+SERIAL_DRIVERLIST = al175 apcsmart bcmxcp belkin belkinunv bestfcom	\
  bestuferrups bestups cyberpower dummy-ups etapro everups 	\
  gamatronic genericups isbmex liebert masterguard megatec metasys 	\
  mge-shut mge-utalk newmge-shut oneac optiups powercom rhino 	\
@@ -92,6 +92,7 @@ upsdrvctl_SOURCES = upsdrvctl.c
 upsdrvctl_LDADD = $(LDADD_COMMON)
 
 # serial drivers: all of them use standard LDADD and CFLAGS
+al175_SOURCES = al175.c
 apcsmart_SOURCES = apcsmart.c
 bcmxcp_SOURCES = bcmxcp.c bcmxcp_ser.c
 belkin_SOURCES = belkin.c
diff --git a/drivers/al175.c b/drivers/al175.c
new file mode 100644
index 0000000..cff7b8f
--- /dev/null
+++ b/drivers/al175.c
@@ -0,0 +1,1372 @@
+/*
+ * al175.c - NUT support for Eltek AL175 alarm module.
+ *           AL175 shall be in COMLI mode.
+ *
+ * Copyright (C) 2004-2008 Marine & Bridge Navigation Systems <http://mns.spb.ru>
+ * Copyright (C) 2004-2008 Kirill Smelkov <kirr at mns.spb.ru>
+ *
+ * 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 <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "main.h"
+#include "serial.h"
+
+#define DRIVER_NAME	"Eltek AL175/COMLI driver"
+#define DRIVER_VERSION	"0.10"
+
+
+/* driver description structure */
+upsdrv_info_t upsdrv_info = {
+	DRIVER_NAME,
+	DRIVER_VERSION,
+	"Kirill Smelkov <kirr at mns.spb.ru>\n" \
+	"Marine & Bridge Navigation Systems <http://mns.spb.ru>",
+	DRV_EXPERIMENTAL,
+	{ NULL }
+};
+
+
+#define	STX	0x02
+#define	ETX	0x03
+#define	ACK	0x06
+
+typedef	unsigned char	byte_t;
+
+/* it's a pity that anonymous variadic macros were introduced only in C99 */
+#define XTRACE(msg) do {				\
+	upsdebugx(1, "%s: %s", __FUNCTION__, msg);	\
+} while (0)
+
+/************
+ * 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  varp  ptr-to local raw_data_t variable to which to alloca
+ * @param  size  the size in bytes
+ * @return alloca'ed memory as raw_data
+ *
+ * it's a pity ISO C forbids braced-groups within expressions
+ */
+#define raw_alloca(varp, size) do {	\
+	(varp)->buf		= alloca(size);	\
+	(varp)->buf_size	= size;		\
+						\
+	(varp)->begin		= (varp)->buf;	\
+	(varp)->end		= (varp)->buf;	\
+} while (0)
+
+
+/**
+ * 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;
+
+	data.buf	= xmalloc(size);
+	data.buf_size	= size;
+
+	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<count; ++i)
+		bcc ^= *((byte_t *)buf + i);
+
+	return bcc;
+}
+
+
+/* XXX: not optimal */
+/**
+ * revers bits in a byte from right to left and vice-versa
+ * @see 6. CODING AND DECODING OF REGISTER VALUES
+ */
+#define REVERSE_BITS(x)	(\
+		( ((x) & 0x80) >> 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;
+
+	h.msg.id	= 0x14;
+	h.msg.stamp	= 1;
+	h.msg.type	= 2;
+
+	h.io.addr	= addr;
+	h.io.len	= count;
+
+	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;
+	char data[8+1];
+
+	h.msg.id	= 0x14;
+	h.msg.stamp	= 1;
+	h.msg.type	= 0;
+
+	h.io.addr	= 0x4500;
+	h.io.len	= 8;
+
+	data[0] = cmd;		/* XXX: shall be ASCII here	*/
+	data[1] = subcmd;
+
+	snprintf(data+2, 6+1, "%2X%2X%2X", pr1, pr2, pr3);
+
+	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; i<io_head->len; ++i)
+		*(io_buf->end++) = reply[11+i];
+
+	reverse_bits(io_buf->begin, (io_buf->end - io_buf->begin) );
+
+	/* debug: dump rx data */
+	if (nut_debug_level > 0) {
+		fprintf(stderr, "rx_buf:\t\t");
+		raw_dump(stderr, *io_buf);
+	}
+
+	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;
+
+	/* debug: dump tx frame */
+	if (nut_debug_level > 0) {
+		fprintf(stderr, "tx:\t\t");
+		raw_dump(stderr, frame);
+	}
+
+	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;
+
+
+	raw_alloca(&ack, 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 (nut_debug_level > 0) {
+		fprintf(stderr, "rx:\t\t");
+		raw_dump(stderr, ack);
+	}
+
+	/* 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;
+
+	raw_alloca(&reply_head, 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 (nut_debug_level > 0) {
+		fprintf(stderr, "rx_head:\t");
+		raw_dump(stderr, reply_head);
+	}
+
+
+	/* 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 (nut_debug_level > 0)
+		fprintf(stderr, "io: %x/%x\n", io->addr, io->len);
+
+	/* 4:  allocate space for full reply and copy header there */
+	raw_alloca(&reply, 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 (nut_debug_level > 0) {
+		fprintf(stderr, "rx:\t\t");
+		raw_dump(stderr, reply);
+	}
+
+	/* 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(&CTRL_frame, 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_data_t rx_data;
+	io_head_t io;
+
+	raw_alloca(&REQ_frame, 512);
+	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 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[0] & 0x40){
+		dstate_setinfo("battery.fuse",	"FAIL");
+		status_set("RB");
+	}else{
+		dstate_setinfo("battery.fuse",	"OK");
+	}
+
+	if (x4000[0] & 0x20){
+		dstate_setinfo("battery.symmetry",	"FAIL");
+		status_set("RB");
+	}else{
+		dstate_setinfo("battery.symmetry",	"OK");
+	}
+
+	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=<some value>' */
+	/* addvar(VAR_VALUE, "foo", "Override foo setting"); */
+}
+
+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("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 */
diff --git a/man/Makefile.am b/man/Makefile.am
index 40918d1..d92add9 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -20,6 +20,7 @@ CGI_PAGES = hosts.conf.5 upsset.conf.5 upsstats.html.5	\
  upsset.cgi.8 upsstats.cgi.8 upsimage.cgi.8
 
 SERIAL_PAGES = \
+	al175.8		\
 	apcsmart.8	\
 	bcmxcp.8 	\
 	belkin.8 	\
diff --git a/man/al175.8 b/man/al175.8
new file mode 100644
index 0000000..3f7be91
--- /dev/null
+++ b/man/al175.8
@@ -0,0 +1,53 @@
+.TH AL175 8 "Wed Sep 21 2005" "" "Network UPS Tools (NUT)"
+.SH NAME
+al175 \- Driver for Eltek UPS models with AL175 alarm module
+.SH NOTE
+This man page only documents the hardware\(hyspecific features of the
+al175 driver. For information about the core driver, see
+\fBnutupsdrv\fR(8).
+
+.SH SUPPORTED HARDWARE
+The al175 driver is known to work with the following UPSes:
+
+    Eltek MPSU4000 with AL175 alarm module
+
+It may also work with other UPSes equiped with AL175 alarm module,
+but they haven't been tested.
+
+
+AL175 have to be configured to operate in COMLI mode.
+See documentation supplied with your hardware on how to do it.
+
+.SH EXTRA ARGUMENTS
+
+This driver does not support any extra settings in the
+\fBups.conf\fR(5).
+
+.SH UPS COMMANDS
+
+This driver supports some extra commands (see \fBupscmd\fR(8)):
+
+.IP "test.battery.start"
+Start a battery test.
+
+.IP "test.battery.stop"
+Stop a battery test.
+
+.SH USER VARIABLES
+
+None
+
+.SH BUGS
+
+Shutdown is not supported.
+
+.SH AUTHOR
+Kirill Smelkov <kirr at mns.spb.ru>
+
+.SH SEE ALSO
+
+.SS The core driver:
+\fBnutupsdrv\fR(8)
+
+.SS Internet resources:
+The NUT (Network UPS Tools) home page: http://www.networkupstools.org/
diff --git a/man/nutupsdrv.8 b/man/nutupsdrv.8
index 2b540b1..b61c29c 100644
--- a/man/nutupsdrv.8
+++ b/man/nutupsdrv.8
@@ -134,7 +134,7 @@ information.
 \fBupsdrvctl\fR(8)
 
 .SS Drivers:
-\fBapcsmart\fR(8), \fBbcmxcp\fR(8), \fBbcmxcp_usb\fR(8),
+-\fBal175\fR(8), \fBapcsmart\fR(8), \fBbcmxcp\fR(8), \fBbcmxcp_usb\fR(8),
 \fBbelkin\fR(8), \fBbelkinunv\fR(8), \fBbestfcom\fR(8),
 \fBbestuferrups\fR(8), \fBbestups\fR(8),
 \fBcyberpower\fR(8), \fBdummy-ups\fR(8), \fBetapro\fR(8),
-- 
tg: (fe47a8f..) t/al175 (depends on: master)



More information about the Nut-upsdev mailing list