Desc: How to make a new driver to support another UPS
File: new-drivers.txt
Date: 30 April 2003
Auth: Russell Kroll <rkroll@exploits.org>

*** April 2003: this document is currently being revised for the new
                socket scheme and variable naming, so refer to driver
                source when in doubt...

Smart vs. Contact-closure
=========================

If your UPS only does contact closure readings, then go straight to the
genericups.txt document for information on adding support.  It's a lot
easier to add a few lines to a header file than it is to create a whole
new driver.

Overall concept
===============

The basic design of drivers is simple.  main.c handles most of the work
for you.  You don't have to worry about arguments, config files, or
anything else like that.  Your only concern is talking to the hardware
and providing data to the outside world.

Skeleton driver
===============

Familiarize yourself with the design of skel.c in the drivers directory.
It shows a few examples of the functions that main will call to obtain
updated information from the hardware.

Essential functions
===================

upsdrv_initups
--------------

Open the port (device_path) and do any low-level things that it may need
to start using that port.  If you have to set DTR or RTS on a serial
port, do it here.

Don't do any sort of hardware detection here, since you may be going
into upsdrv_shutdown next.

upsdrv_initinfo
---------------

Try to detect what kind of UPS is out there, if any, assuming that's
possible for your hardware.  If there is a way to detect that hardware
and it doesn't appear to be connected, display an error and exit.

Otherwise, start calling setinfo() to establish the data you will be
monitoring.

upsdrv_updateinfo
-----------------

Poll the hardware, and update any variables that you care about
monitoring.  Use setinfo() to store the new values.

Do at most one pass of the variables.  You MUST return from this
function or upsd will be unable to read data from your driver.  main
will call this function at regular intervals.

upsdrv_shutdown
---------------

Do whatever you can to make the UPS power off the load but also return
after the power comes back on.  You may use a different command that
keeps the UPS off if the user has requested that with a configuration
setting.

You should attempt the UPS shutdown command even if the UPS detection
fails.  If the UPS does not shut down the load, then the user is
vulnerable to a race if the power comes back on during the shutdown
process.

Data types
==========

To be of any use, you must supply STATUS data.  That is the minimum
needed to let upsmon do its job.  Whenever possible, you should also
provide anything else that can be monitored by the driver.  Some obvious
things are the manufacturer name and model name, voltage data, and so
on.

If you can't figure out some value automatically, use the ups.conf
options to let the user tell you.  This can be useful when a driver
needs to support many similar hardware models but can't probe to see
what is actually attached.

Manipulating the data
=====================

All status data lives in structures that are managed by the dstate
functions.  All access and modifications must happen through those
functions.  Any other changes are forbidden, as they will not pushed out
as updates to things like upsd.

Adding variables
----------------

	dstate_setinfo("ups.model", "Mega-Zapper 1500");

Many of these functions take format strings, so you can build the new
values right there:

	dstate_setinfo("ups.model", "Mega-Zapper %d", rating);

Setting flags
-------------

Some variables have special properties.  They can be writable, and some
are strings.  The ST_FLAG_* values can be used to tell upsd more about
what it can do.

	dstate_setflags("input.transfer.high", ST_FLAG_RW);

Serial port handling
====================

 - void open_serial_simple(const char *port, speed_t speed, int flags)

   This will open the named serial port at a given speed and will
   set up the control lines according to 'flags'.  This does almost
   no manipulation of the tty layer and probably will not work for
   RS-232 data unless you fix the port up yourself.

 - void open_serial(const char *port, speed_t speed)

   This is like open_serial_simple, but it configures the tty layer for
   data passing.  You will probably need to use this one for any 
   driver that expects to send and receive normal serial data.

 - int upsrecv(char *buf, int buflen, char endchar, const char *ignchars)
   
   This will wait up to 3 seconds for data to arrive from the UPS.
   It ignores any characters in ignchars, and returns when endchar is
   received.  Data is stored in buf, up to buflen bytes.

 - int upsrecvchars(char *buf, int buflen)

   This is a simpler receive function to bring in a fixed number of
   bytes.  A 3 second timeout is also used, but no other parsing of
   characters occurs.

 - int upssendchar(char data)

   Send a single character to the UPS.

 - int upssend(const char *fmt, ...)

   Send a formatted buffer to the UPS.

 - void upsflushin(int expected, int debugit, const char *ignchars)

   Use this to clear out the input queue.  This can be useful when the
   hardware is known to return unexpected data in between polls.

Variable names
==============

This list currently is found in new-names.txt.

Message passing support
-----------------------

See commands.txt.

Enumerated types
----------------

If you have a variable that can have several specific entries, it is
enumerated.  You should add each one to make it available to the client:

	dstate_addenum("input.transfer.low", "92");
	dstate_addenum("input.transfer.low", "95");
	dstate_addenum("input.transfer.low", "99");
	dstate_addenum("input.transfer.low", "105");

Writable strings
----------------

Strings that may be changed by the client should have the ST_FLAG_STRING
flag set, and a maximum length byte set in the auxdata.

	dstate_setinfo("ups.id", "Big UPS");
	dstate_setflags("ups.id", ST_FLAG_STRING | ST_FLAG_RW);
	dstate_setaux("ups.id", 8);

If the variable is not writable, don't bother with the flags or the
auxiliary data.  It won't be used.

Instant commands
----------------

If your hardware can support a command, register it.

	dstate_addcmd("load.on");
