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

Kirill Smelkov kirr at mns.spb.ru
Tue Jan 13 14:58:23 UTC 2009


Arjen, Arnaud,
first of all, I'm sorry for my late reply.

If it's not too late, here is updated al175:


On Fri, Dec 26, 2008 at 11:37:04PM +0100, Arjen de Korte wrote:
> Citeren Kirill Smelkov <kirr at mns.spb.ru>:
>
> [...]
>
>> Yes, "All the world is not an x86 Linux box," and I've tried to make all the
>> world happy.
>
> I raised several other issues as well, of which the most important one  
> isn't addressed. The driver still uses 'alloca' which isn't portable (it 
> is not in POSIX). I also indicated how this could be solved by using 
> automatic variables. In any case I'm not conviced there is no  
> alternative here, so this is a show stopper for now.

I've reworked it not to use alloca and to use auto-variables + xmalloc
in one place.

Now it usually looks like

    raw_data    reply;
    byte_t      reply_buf[11];

    ...

    raw_alloc_onstack(&reply, reply_buf);

> Also something I really don't like in this driver is the use of alarm(). 
> Although this is presently used in the 'net-xml' driver (in case an older 
> neon library is detected), there is no need for this here. We have 
> timeouts on *all* ser_get* functions and these *must* be used instead. 
> Note that with using timeouts I mean something different than setting 
> them to '999' seconds and setting an alarm() to breakout early. Unless 
> there is a really good reason to use an alarm() (ie, there is no 
> alternative), this shouldn't be used.

I've used alarm in the first place for the following reason:

Let's look at e.g. upsdrv_updateinfo() -- it reads several al175
registers, and each read_register consists of a chat between host and
al175.

And I want the whole transaction to be less than say 5 seconds. What
should I do? manually calculate read/write time budget left for _each_
read or write?

I think this is not practical.

That's why I simply

1. arm the timer via alarm(T) to fire after T seconds
2. do all the I/O, logic, etc...
3. if I/O would stale at *any* place, it will be *always* aborted right
   after T seconds since the transaction start (thanks to alarm)
4. after all the I/O transaction is done -- reset the timer.

And exactly for this reason 999 is used as infinity which means 'much
more longer than _any_ T we'll pass to alarm() ever'. If there is a way
to actually pass 'please never timeout on select', I'd be happy to
rework the code.

Also according to mans, alarm(2) is in SVr4, POSIX.1-2001 and 4.3BSD.
That's why I think using alarm() makes sense.

But if there is a similiar functionality in NUT to set whole transaction
timeout, I'll be happy to rework the code to use what NUT provides.

> Even in the updated driver, I see quite a couple of fprintf() lines.  
> Although these are in most (all?) cases encased in a 'if  
> (nut_debug_level > 0)', we have the upsdebug* functions for that. Do  
> yourself (and your fellow developers) a favor and use those instead.  

> Another thing related to this is the XTRACE macro. This will only use a 
> single (1) debug level to show output. Please use a layered approach, 
> where various levels of debugging information are used.

I've split debug/trace output into several levels:

    1       /* user-level trace (status, instcmd, etc */
    2       /* proto handling info  */
    3       /* IO trace             */


so with -D the user will see only high level stuff, with -DD --
high-level info + any information regarding COMLI proto handling, and
with -DDD -- everything includiong full IO trace.

Can't test it because I don't have the hardware now, but I hope this is
ok.

(one place still uses fprintf(stderr, ...) but that's because it is
 difficult to construct one line through several calls to upsdebug,
 although sure this is doable, like upsdebug_hex does. Mine has pretty
 printing for control ascii characters, and I'd better leave it there,
 because as I recall this was usefull for debugging. I hope this is not
 a show-stopper.)


> It also helps understanding the driver if you keep the number of
> macro's limited.

The reason XTRACE was introduced in the first place is to automatically
put a function name as prefix into trace output through the use of
__FUNCTION__. if it is (again) a protability problem, we could always
workaround this as follows (taken from gcc manual):


     #if __STDC_VERSION__ < 199901L
     # if __GNUC__ >= 2
     #  define __func__ __FUNCTION__
     # else
     #  define __func__ "<unknown>"
     # endif
     #endif



Here is the interdiff wrt recent version as posted to the list, and the
full patch:





(interdiff)

diff --git a/drivers/al175.c b/drivers/al175.c
index cff7b8f..defda2f 100644
--- a/drivers/al175.c
+++ b/drivers/al175.c
@@ -51,11 +51,35 @@ upsdrv_info_t upsdrv_info = {
 
 typedef	unsigned char	byte_t;
 
+
+/*
+ * here is how AL175 supports debugging/tracing
+ */
+
+#define AL175_DUSER	1	/* user-level trace (status, instcmd, etc...) */
+#define AL175_DPROTO	2	/* proto handling info	*/
+#define AL175_DIO	3	/* IO trace		*/
+
 /* it's a pity that anonymous variadic macros were introduced only in C99 */
-#define XTRACE(msg) do {				\
-	upsdebugx(1, "%s: %s", __FUNCTION__, msg);	\
+#define __XTRACE(level, msg) do {				\
+	upsdebugx(level, "%s: %s", __FUNCTION__, msg);	\
 } while (0)
 
+/* AL175_DUSER is usually done through explicit upsdebugx() -- we almost always
+ * want to use printf like formatting, and it is not avaliable when using
+ * macroses
+ */
+#define XPROTO(msg)	do { __XTRACE(AL175_DPROTO, msg); } while (0)
+#define XIO(msg)	do { __XTRACE(AL175_DIO,    msg); } while (0)
+#define	XIODUMP(msg, data) do {			\
+	if (nut_debug_level >= AL175_DIO) {		\
+		fprintf(stderr, (msg));		\
+		raw_dump(stderr, (data));	\
+	}					\
+} while (0)
+
+
+
 /************
  * RAW DATA *
  ************/
@@ -88,19 +112,20 @@ typedef	raw_data_t	const_raw_data_t;
 
 
 /**
- * alloca raw_data buffer
- * @param  varp  ptr-to local raw_data_t variable to which to alloca
- * @param  size  the size in bytes
+ * alloca raw_data buffer  (portable version)
+ * @param  varp		ptr-to local raw_data_t variable to which to alloca
+ * @param  buf_array	array allocated on stack which will be used as storage
+ *			(must be auto-variable)
  * @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;	\
+#define raw_alloc_onstack(varp, buf_array) do {		\
+	(varp)->buf		= &(buf_array)[0];	\
+	(varp)->buf_size	= sizeof(buf_array);	\
+							\
+	(varp)->begin		= (varp)->buf;		\
+	(varp)->end		= (varp)->buf;		\
 } while (0)
 
 
@@ -177,6 +202,9 @@ static const char* ascii_symb[] = {
  * dump raw_data buffer to file stream
  * @param  out  output stream
  * @param  buf  the buffer to dump
+ *
+ * XXX I'd be happy to use upsdebug_hex, but it lacks support for
+ * pretty-printing of ascii_symb'ols, so lets live it here for now.
  */
 void raw_dump(FILE *out, const_raw_data_t buf)
 {
@@ -520,39 +548,39 @@ static int al_parse_reply_head(io_head_t *io, const raw_data_t raw_reply_head)
 	const byte_t *reply_head = raw_reply_head.begin - 1;
 
 	if ( (raw_reply_head.end - raw_reply_head.begin) != 10)  {
-		XTRACE("wrong size");
+		XPROTO("wrong size");
 		return -1;		/* wrong size	*/
 	}
 
 	if (reply_head[1]!='0' || reply_head[2]!='0')  {
-		XTRACE("wrong id");
+		XPROTO("wrong id");
 		return -1;		/* wrong id	*/
 	}
 
 	if (reply_head[3]!='1')  {
-		XTRACE("wrong stamp");
+		XPROTO("wrong stamp");
 		return -1;		/* wrong stamp	*/
 	}
 
 	if (reply_head[4]!='0')  {
-		XTRACE("wrong type");
+		XPROTO("wrong type");
 		return -1;		/* wrong type	*/
 	}
 
 	io_addr = from_hex(&reply_head[5], 4);
 	if (io_addr==-1UL)  {
-		XTRACE("wrong addr");
+		XPROTO("wrong addr");
 		return -1;		/* wrong addr	*/
 	}
 
 	io_len = from_hex(&reply_head[9], 2);
 	if (io_len==-1UL)   {
-		XTRACE("wrong nob");
+		XPROTO("wrong nob");
 		return -1;		/* wrong NOB	*/
 	}
 
 	if (io_len > IO_LEN_MAX) {
-		XTRACE("nob too big");
+		XPROTO("nob too big");
 		return -1;		/* too much data claimed */
 	}
 
@@ -606,14 +634,14 @@ static int al_parse_reply(io_head_t *io_head, raw_data_t *io_buf, const_raw_data
 	reply = raw_reply.begin - 1;
 
 	if ( (raw_reply.end - raw_reply.begin) != (ptrdiff_t)(10 + io_head->len))  {
-		XTRACE("corrupt sentence");
+		XPROTO("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.");
+		XPROTO("too much data to fit in io_buf.");
 		return -1;		/* too much data to fit in io_buf	*/
 	}
 
@@ -626,10 +654,7 @@ static int al_parse_reply(io_head_t *io_head, raw_data_t *io_buf, const_raw_data
 	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);
-	}
+	XIODUMP("rx_buf:\t\t", *io_buf);
 
 	return 0;	/* all ok */
 }
@@ -657,12 +682,12 @@ static int al_check_ack(const_raw_data_t raw_ack)
 	const byte_t *ack = raw_ack.begin - 1;
 
 	if ( (raw_ack.end - raw_ack.begin) !=5)  {
-		XTRACE("wrong size");
+		XPROTO("wrong size");
 		return -1;		/* wrong size	*/
 	}
 
 	if (ack[1]!='0' || ack[2]!='0')  {
-		XTRACE("wrong id");
+		XPROTO("wrong id");
 		return -1;		/* wrong id	*/
 	}
 
@@ -672,17 +697,17 @@ static int al_check_ack(const_raw_data_t raw_ack)
 	 * is hardcoded here.
 	 */
 	if (ack[3]!='1')  {
-		XTRACE("wrong stamp");
+		XPROTO("wrong stamp");
 		return -1;		/* wrong stamp	*/
 	}
 
 	if (ack[4]!='1')  {
-		XTRACE("wrong type");
+		XPROTO("wrong type");
 		return -1;		/* wrong type	*/
 	}
 
 	if (ack[4]!=ACK)  {
-		XTRACE("wrong ack");
+		XPROTO("wrong ack");
 		return -1;		/* wrong ack	*/
 	}
 
@@ -704,7 +729,7 @@ static int al_check_ack(const_raw_data_t raw_ack)
 static void alarm_handler(int sig)
 {
 	/* just do nothing */
-	XTRACE("timeout...\n");		/* XXX: doing so is wrong from signal handler... */
+	XIO("timeout...\n");		/* XXX: doing so is wrong from signal handler... */
 }
 
 /* clear any flow control (stolen from powercom.c) */
@@ -718,7 +743,7 @@ static void ser_disable_flow_control (void)
 	tio.c_cc[VSTART] = _POSIX_VDISABLE;
 	tio.c_cc[VSTOP] = _POSIX_VDISABLE;
 
-	upsdebugx(2, "Flow control disable");
+	XIO("Flow control disable");
 
 	/* disable any flow control */
 	tcsetattr(upsfd, TCSANOW, &tio);
@@ -740,10 +765,7 @@ 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);
-	}
+	XIODUMP("tx:\t\t", frame);
 
 	err = ser_send_buf(upsfd, frame.begin, (frame.end - frame.begin) );
 	if (err==-1) {
@@ -809,6 +831,7 @@ static int recv_command_ack()
 {
 	int err;
 	raw_data_t ack;
+	byte_t     ack_buf[8];
 
 	/* 1:  STX  */
 	err = scan_for(STX);
@@ -816,7 +839,7 @@ static int recv_command_ack()
 		return -1;
 
 
-	raw_alloca(&ack, 8);
+	raw_alloc_onstack(&ack, ack_buf);
 	*(ack.end++) = STX;
 
 
@@ -828,10 +851,7 @@ static int recv_command_ack()
 	ack.end += 7;
 
 	/* frame constructed - let's verify it */
-	if (nut_debug_level > 0) {
-		fprintf(stderr, "rx:\t\t");
-		raw_dump(stderr, ack);
-	}
+	XIODUMP("rx:\t\t", ack);
 
 	/* generic layout */
 	err = comli_check_frame(ack);
@@ -854,16 +874,18 @@ static int recv_command_ack()
  */
 static int recv_register_data(io_head_t *io, raw_data_t *io_buf)
 {
-	int err;
+	int err, ret;
 	raw_data_t reply_head;
 	raw_data_t reply;
 
+	byte_t	   reply_head_buf[11];
+
 	/* 1:  STX  */
 	err = scan_for(STX);
 	if (err==-1)
 		return -1;
 
-	raw_alloca(&reply_head, 11);
+	raw_alloc_onstack(&reply_head, reply_head_buf);
 	*(reply_head.end++) = STX;
 
 
@@ -874,10 +896,7 @@ static int recv_register_data(io_head_t *io, raw_data_t *io_buf)
 
 	reply_head.end += 10;
 
-	if (nut_debug_level > 0) {
-		fprintf(stderr, "rx_head:\t");
-		raw_dump(stderr, reply_head);
-	}
+	XIODUMP("rx_head:\t", reply_head);
 
 
 	/* 3:  check header, extract IO info */
@@ -889,11 +908,10 @@ static int recv_register_data(io_head_t *io, raw_data_t *io_buf)
 
 	reply_head.begin -= 1;  /* restore STX */
 
-	if (nut_debug_level > 0)
-		fprintf(stderr, "io: %x/%x\n", io->addr, io->len);
+	upsdebugx(AL175_DIO, "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*/);
+	reply = raw_xmalloc(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);
@@ -901,24 +919,21 @@ static int recv_register_data(io_head_t *io, raw_data_t *io_buf)
 	/* 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;
+		XIO("rx_tail failed");
+		ret = -1; goto out;
 	}
 
 	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);
-	}
+	XIODUMP("rx:\t\t", reply);
 
 	/* generic layout */
 	err = comli_check_frame(reply);
 	if (err==-1) {
-		XTRACE("corrupt frame");
-		return -1;
+		XPROTO("corrupt frame");
+		ret = -1; goto out;
 	}
 
 	/* shrink frame */
@@ -927,7 +942,11 @@ static int recv_register_data(io_head_t *io, raw_data_t *io_buf)
 
 
 	/* XXX: a bit of processing duplication here	*/
-	return al_parse_reply(io, io_buf, reply);
+	ret = al_parse_reply(io, io_buf, reply);
+
+out:
+	raw_free(&reply);
+	return ret;
 }
 
 
@@ -946,8 +965,9 @@ static int al175_do(int cmd, int subcmd, int pr1, int pr2, int pr3)
 {
 	int err;
 	raw_data_t CTRL_frame;
+	byte_t	   CTRL_frame_buf[512];
 
-	raw_alloca(&CTRL_frame, 512);
+	raw_alloc_onstack(&CTRL_frame, CTRL_frame_buf);
 	al_prep_activate(&CTRL_frame, cmd, subcmd, pr1, pr2, pr3);
 
 	flush_rx_queue();		/*  DROP  */
@@ -972,7 +992,9 @@ static int al175_read(byte_t *dst, unsigned addr, size_t count)
 	raw_data_t rx_data;
 	io_head_t io;
 
-	raw_alloca(&REQ_frame, 512);
+	byte_t	REQ_frame_buf[512];
+
+	raw_alloc_onstack(&REQ_frame, REQ_frame_buf);
 	al_prep_read_req(&REQ_frame, addr, count);
 
 	flush_rx_queue();		/*  DROP  */
@@ -998,7 +1020,7 @@ static int al175_read(byte_t *dst, unsigned addr, size_t count)
 		return -1;
 
 	if ( (io.addr != addr) || (io.len != count) ) {
-		XTRACE("io_head mismatch");
+		XPROTO("io_head mismatch");
 		return -1;
 	}
 
@@ -1078,7 +1100,7 @@ void upsdrv_updateinfo(void)
 	double batt_current = 0.0;
 
 
-	fprintf(stderr, "\nUPDATEINFO\n");
+	XIO("\nUPDATEINFO\n");
 	alarm(3);
 
 #define	RECV(reg) do { \
@@ -1228,7 +1250,7 @@ void upsdrv_updateinfo(void)
 
 	status_commit();
 
-	upsdebugx(1, "STATUS: %s", dstate_getinfo("ups.status"));
+	upsdebugx(AL175_DUSER, "STATUS: %s", dstate_getinfo("ups.status"));
 	dstate_dataok();
 
 
@@ -1281,7 +1303,7 @@ static int instcmd(const char *cmdname, const char *extra)
 {
 	int err;
 
-	upsdebugx(1, "INSTCMD: %s", cmdname);
+	upsdebugx(AL175_DUSER, "INSTCMD: %s", cmdname);
 
 	alarm(5);
 	err = instcmd_worker(cmdname);



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(The patch itself)

From: Kirill Smelkov <kirr at mns.spb.ru>
Subject: [PATCH] al175: updated driver, please restore it

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:

- alloca was eliminated through the help of automatic variables
- debugging/tracing were reworked to (almost always) use NUT builtins
- al175 now uses 3 debug levels for (1=user-level info, 2=protocol debugging,
  3=I/O tracing)

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

Thanks,
Kirill.

 MAINTAINERS         |    4 +
 data/driver.list    |    2 +
 docs/al175-vars.txt |   28 +
 drivers/Makefile.am |    3 +-
 drivers/al175.c     | 1394 +++++++++++++++++++++++++++++++++++++++++++++++++++
 man/Makefile.am     |    1 +
 man/al175.8         |   53 ++
 man/nutupsdrv.8     |    1 +
 8 files changed, 1485 insertions(+), 1 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 030b331..24f1649 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 91ed5dd..1d1aa62 100644
--- a/drivers/Makefile.am
+++ b/drivers/Makefile.am
@@ -32,7 +32,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 	\
@@ -88,6 +88,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..defda2f
--- /dev/null
+++ b/drivers/al175.c
@@ -0,0 +1,1394 @@
+/*
+ * 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;
+
+
+/*
+ * here is how AL175 supports debugging/tracing
+ */
+
+#define AL175_DUSER	1	/* user-level trace (status, instcmd, etc...) */
+#define AL175_DPROTO	2	/* proto handling info	*/
+#define AL175_DIO	3	/* IO trace		*/
+
+/* it's a pity that anonymous variadic macros were introduced only in C99 */
+#define __XTRACE(level, msg) do {				\
+	upsdebugx(level, "%s: %s", __FUNCTION__, msg);	\
+} while (0)
+
+/* AL175_DUSER is usually done through explicit upsdebugx() -- we almost always
+ * want to use printf like formatting, and it is not avaliable when using
+ * macroses
+ */
+#define XPROTO(msg)	do { __XTRACE(AL175_DPROTO, msg); } while (0)
+#define XIO(msg)	do { __XTRACE(AL175_DIO,    msg); } while (0)
+#define	XIODUMP(msg, data) do {			\
+	if (nut_debug_level >= AL175_DIO) {		\
+		fprintf(stderr, (msg));		\
+		raw_dump(stderr, (data));	\
+	}					\
+} 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  (portable version)
+ * @param  varp		ptr-to local raw_data_t variable to which to alloca
+ * @param  buf_array	array allocated on stack which will be used as storage
+ *			(must be auto-variable)
+ * @return alloca'ed memory as raw_data
+ *
+ * it's a pity ISO C forbids braced-groups within expressions
+ */
+#define raw_alloc_onstack(varp, buf_array) do {		\
+	(varp)->buf		= &(buf_array)[0];	\
+	(varp)->buf_size	= sizeof(buf_array);	\
+							\
+	(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
+ *
+ * XXX I'd be happy to use upsdebug_hex, but it lacks support for
+ * pretty-printing of ascii_symb'ols, so lets live it here for now.
+ */
+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)  {
+		XPROTO("wrong size");
+		return -1;		/* wrong size	*/
+	}
+
+	if (reply_head[1]!='0' || reply_head[2]!='0')  {
+		XPROTO("wrong id");
+		return -1;		/* wrong id	*/
+	}
+
+	if (reply_head[3]!='1')  {
+		XPROTO("wrong stamp");
+		return -1;		/* wrong stamp	*/
+	}
+
+	if (reply_head[4]!='0')  {
+		XPROTO("wrong type");
+		return -1;		/* wrong type	*/
+	}
+
+	io_addr = from_hex(&reply_head[5], 4);
+	if (io_addr==-1UL)  {
+		XPROTO("wrong addr");
+		return -1;		/* wrong addr	*/
+	}
+
+	io_len = from_hex(&reply_head[9], 2);
+	if (io_len==-1UL)   {
+		XPROTO("wrong nob");
+		return -1;		/* wrong NOB	*/
+	}
+
+	if (io_len > IO_LEN_MAX) {
+		XPROTO("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))  {
+		XPROTO("corrupt sentence");
+		return -1;		/* corrupt sentence	*/
+	}
+
+
+	/* extract the data */
+	if (io_buf->buf_size < io_head->len)	{  /* XXX: maybe write from io_buf->begin ?	*/
+		XPROTO("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 */
+	XIODUMP("rx_buf:\t\t", *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)  {
+		XPROTO("wrong size");
+		return -1;		/* wrong size	*/
+	}
+
+	if (ack[1]!='0' || ack[2]!='0')  {
+		XPROTO("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')  {
+		XPROTO("wrong stamp");
+		return -1;		/* wrong stamp	*/
+	}
+
+	if (ack[4]!='1')  {
+		XPROTO("wrong type");
+		return -1;		/* wrong type	*/
+	}
+
+	if (ack[4]!=ACK)  {
+		XPROTO("wrong ack");
+		return -1;		/* wrong ack	*/
+	}
+
+
+	return 0;
+}
+
+
+
+
+
+/******************************************************************/
+
+
+/**********
+ * SERIAL *
+ **********/
+
+static void alarm_handler(int sig)
+{
+	/* just do nothing */
+	XIO("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;
+
+	XIO("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 */
+	XIODUMP("tx:\t\t", 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;
+	byte_t     ack_buf[8];
+
+	/* 1:  STX  */
+	err = scan_for(STX);
+	if (err==-1)
+		return -1;
+
+
+	raw_alloc_onstack(&ack, ack_buf);
+	*(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 */
+	XIODUMP("rx:\t\t", 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, ret;
+	raw_data_t reply_head;
+	raw_data_t reply;
+
+	byte_t	   reply_head_buf[11];
+
+	/* 1:  STX  */
+	err = scan_for(STX);
+	if (err==-1)
+		return -1;
+
+	raw_alloc_onstack(&reply_head, reply_head_buf);
+	*(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;
+
+	XIODUMP("rx_head:\t", 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 */
+
+	upsdebugx(AL175_DIO, "io: %x/%x\n", io->addr, io->len);
+
+	/* 4:  allocate space for full reply and copy header there */
+	reply = raw_xmalloc(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)) {
+		XIO("rx_tail failed");
+		ret = -1; goto out;
+	}
+
+	reply.end += io->len + 2;
+
+
+	/* frame constructed, let's verify it */
+	XIODUMP("rx:\t\t", reply);
+
+	/* generic layout */
+	err = comli_check_frame(reply);
+	if (err==-1) {
+		XPROTO("corrupt frame");
+		ret = -1; goto out;
+	}
+
+	/* shrink frame */
+	reply.begin += 1;
+	reply.end   -= 2;
+
+
+	/* XXX: a bit of processing duplication here	*/
+	ret = al_parse_reply(io, io_buf, reply);
+
+out:
+	raw_free(&reply);
+	return ret;
+}
+
+
+/*****************************************************************/
+
+/*********************
+ * 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;
+	byte_t	   CTRL_frame_buf[512];
+
+	raw_alloc_onstack(&CTRL_frame, CTRL_frame_buf);
+	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;
+
+	byte_t	REQ_frame_buf[512];
+
+	raw_alloc_onstack(&REQ_frame, REQ_frame_buf);
+	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) ) {
+		XPROTO("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;
+
+
+	XIO("\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(AL175_DUSER, "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(AL175_DUSER, "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 54510d6..c85d41c 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 e57dc93..550a7b7 100644
--- a/man/nutupsdrv.8
+++ b/man/nutupsdrv.8
@@ -134,6 +134,7 @@ information.
 \fBupsdrvctl\fR(8)
 
 .SS Drivers:
+\fBal175\fR(8),
 \fBapcsmart\fR(8),
 \fBbcmxcp\fR(8),
 \fBbcmxcp_usb\fR(8),
-- 
tg: (e970c3d..) t/al175 (depends on: master)


Thanks,
Kirill



More information about the Nut-upsdev mailing list