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

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

If your UPS only does contact closure readings, then go straight to the
contact-closure.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.

This is usually a good place to create variables like ups.mfr,
ups.model, ups.serial, and other "one time only" items.

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

Poll the hardware, and update any variables that you care about
monitoring.  Use dstate_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.

If your UPS hardware is slow or brain-damaged, you might use an 
odd/even approach to this function.  Half the time, set a flag and
return.  The other half, clear the flag and actually do the poll.

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.

Flags for main.c
================

Set experimental_driver to 1 if your driver is potentially broken.  This
will trigger a warning when it starts so the user doesn't take it for
granted.

Setting broken_driver to 1 will cause main to print an error and exit.
This is only used during conversions of the driver core to keep users
from using drivers which have not been converted.  Drivers in this state 
will be removed from the tree after some period if they are not fixed.

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

To be of any use, you must supply data in ups.status.  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);

Status data
===========

UPS status flags like on line (OL) and on battery (OB) live in
ups.status.  Don't manipulate this by hand.  There are functions which
will do this for you.

        status_init() - before doing anything else

        status_set(val) - add a status word (OB, OL, etc)

        status_commit() - push out the update

Possible values for status_set:

 OL     - On line
 OB     - On battery (inverter is providing load power)
 LB     - Low battery
 RB     - The battery needs to be replaced
 BYPASS - UPS bypass circuit is active - no battery protection is available
 CAL    - UPS is currently performing runtime calibration (on battery)
 OFF    - UPS is offline and is not supplying power to the load
 OVER   - UPS is overloaded
 TRIM   - UPS is trimming incoming voltage (called "buck" in some hardware)
 BOOST  - UPS is boosting incoming voltage

Anything else will not be recognized by the usual clients.  Coordinate
with me before creating something new, since there will be duplication
and ugliness otherwise.

Note: upsd injects "FSD" by itself following that command by a master
upsmon process.  Drivers must not set that value.

UPS alarms
==========

These work like ups.status, and have three special functions which you
must use to manage them.

	alarm_init() - before doing anything else

	alarm_set() - add an alarm word

	alarm_commit() - push the value into ups.alarm
                         also updates ups.status

Note: the ALARM flag in ups.status is automatically set whenever you use
alarm_set.  To remove that flag from ups.status, call alarm_init and
alarm_commit without calling alarm_set in the middle.

You should never try to set or unset the ALARM flag manually.

There is no official list of alarm words as of this writing, so don't
use these functions until you check with the upsdev list.

Staleness control
=================

 - dstate_dataok() 

   You must call this regularly if polls are succeeding.  A good place
   to call this is the bottom of upsdrv_updateinfo().

   If you're not talking to a polled UPS, then you must ensure that it
   is still out there and is alive before calling dstate_dataok().  Even
   if nothing is changing, you should still "ping" it or do something
   else to ensure that it is really available.  If the attempts to
   contact the UPS fail, you must call dstate_datastale() to inform the
   server and clients.

 - dstate_datastale()

   You must call this if your status is unusable.  A good technique is
   to call this before exiting prematurely from upsdrv_updateinfo().

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

Drivers which use serial port functions should include serial.h and use
these functions whenever possible:


 - int ser_open(const char *port)

This opens the port and locks it if possible, using one of fcntl, lockf,
or uu_lock depending on what may be available.  If something fails, it
calls fatal for you.  If it succeeds, it always returns the fd that was
opened.


 - int ser_set_speed(int fd, const char *port, speed_t speed)

This sets the speed of the port and also does some basic configuring
with tcgetattr and tcsetattr.  If you have a special serial
configuration (other than 8N1), then this may not be what you want.

The port name is provided again here so failures in tcgetattr() provide
a useful error message.  This is the only place that will generate a
message if someone passes a non-serial port /dev entry to your driver,
so it needs the extra detail.


 - int ser_close(int fd, const char *port)

This function unlocks the port if possible and closes the fd.  You
should call this in your upsdrv_cleanup handler.


 - int ser_send_char(int fd, char ch)

This attempts to write one character and returns the return value from
write.  You could call write directly, but using this function allows
for future error handling in one place.


 - unsigned int ser_send_pace(int fd, unsigned long d_usec, 
	const char *fmt, ...)

If you need to send a formatted buffer with an intercharacter delay, use
this function.  There are a number of UPS controllers which can't take
commands at the full speed that would normally be possible at a given
bit rate.  Adding a small delay usually turns a flaky UPS into a solid
one.

The return value is the number of characters that was sent to the port,
or -1 if something failed.


 - unsigned int ser_send(int fd, const char *fmt, ...)

Like ser_send_pace, but without a delay.  Only use this if you're sure
that your UPS can handle characters at the full line rate.


 - int ser_send_buf(int fd, const char *buf, size_t buflen)

This sends a raw buffer to the fd.  It is typically used for binary
transmissions.  It returns the results of the call to write.


 - int ser_send_buf_pace(int fd, unsigned long d_usec, const char *buf, 
	size_t buflen)

This is just ser_send_buf with an intercharacter delay.


 - int ser_get_char(int fd, char *ch, long d_sec, long d_usec)

This will wait up to d_sec seconds + d_usec microseconds for one
character to arrive, storing it at ch.  It returns 1 on success, or -1
if something fails, including a timeout.

Note: the delay value must not be too large, or your driver will not get
back to the usual idle loop in main in time to answer the PINGs from
upsd.  That will cause an oscillation between staleness and normal
behavior.


 - int ser_get_buf_len(int fd, char *buf, size_t buflen, long d_sec,
	long d_usec)

Like ser_get_char, but this one waits for buflen bytes to arrive,
storing all of them in buf.  The buffer is zeroed regardless of success
or failure.  It returns the number of bytes read or -1 on failure,
including a timeout.

This should only be used for binary reads.  See ser_get_line for
protocols that are terminated by characters like CR or LF.


 - int ser_get_line(int fd, char *buf, size_t buflen, char endchar,
	const char *ignset, long d_sec, long d_usec)

This is the reading function you should use if your UPS tends to send
responses like "OK\r" or "1234\n".  It reads up to buflen bytes and
stores them in buf, but it will return immediately if it encounters
endchar.  The endchar will not be stored in the buffer.  It will also
return if it manages to collect a full buffer before reaching the
endchar.  

If the character matches the ignset with strchr(), it will not be added
to the buffer.  If you don't need to ignore any characters, just pass it
an empty string - "".

The buffer is always cleared and is always null-terminated.  It does
this by reading at most (buflen - 1) bytes.

Note: any other data which is read after the endchar in the serial
buffer will be lost forever.  As a result, you should not use this 
unless your UPS uses a polled protocol.

Let's say your endchar is \n and your UPS sends "OK\n1234\nabcd\n".   
This function will read() all of that, find the first \n, and stop
there.  Your driver will get "OK", and the rest is gone forever.

This also means that you should not "pipeline" commands to the UPS.
Send a query, then read the response, then send the next query.


 - int ser_get_line_alert(int fd, char *buf, size_t buflen, 
	char endchar, const char *ignset, const char *alertset, 
	void handler(char ch), long d_sec, long d_usec)

This is just like ser_get_line, but it allows you to specify a set of
alert characters which may be received at any time.  They are not added
to the buffer, and this function will call your handler function,
passing the character as an argument.

Implementation note: this function actually does all of the work, and
ser_get_line is just a wrapper that sets an empty alertset and a NULL
handler.


 - int ser_flush_in(int fd, const char *ignset, int verbose)

This function will drain the input buffer.  If verbose is set to a
positive number, then it will announce the characters which have been
read in the syslog.  You should not set verbose unless debugging is
enabled, since it could be very noisy.

This function returns the number of characters which were read, so you
can check for extra bytes by looking for a nonzero return value.  Zero
will also be returned if the read fails for some reason.


 - void ser_comm_fail(const char *fmt, ...)

Call this whenever your serial communications fail for some reason.  It
takes a format string, so you can use variables and other things to
clarify the error.  This function does built-in rate-limiting so you
can't spam the syslog.

By default, it will write 10 messages, then it will stop and only write
1 in 100.  This allows the driver to keep calling this function while
the problem persists without filling the logs too quickly.

In the old days, drivers would report a failure once, and then would be
silent until things were fixed again.  Users had to figure out what was
happening by finding that single error message, or by looking at the
repeated complaints from upsd or the clients.

If your UPS frequently fails to acknowledge polls and this is a known
situation, you should make a couple of attempts before calling this
function.

Note: this does not call dstate_datastale.  You still need to do that.


 - void ser_comm_good(void)

This will clear the error counter and write a "re-established" message
to the syslog after communications have been lost.  Your driver should
call this whenever it has successfully contacted the UPS.  A good place
for most drivers is where it calls dstate_dataok.

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

PLEASE don't make up new variables and commands just because you can.
The new dstate functions give us the power to create just about
anything, but that is a privilege and not a right.  Imagine the mess
that would happen if every developer decided on their own way to
represent a common status element.

Check new-names.txt first to find the closest fit.  If nothing matches,
contact the upsdev list or mail me directly, and we'll figure it out.

Patches which introduce unlisted names may be modified or dropped.

Message passing support
=======================

See commands.txt.

Enumerated types
================

If you have a variable that can have several specific values, 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 and driver can support a command, register it.

	dstate_addcmd("load.on");

Delays and ser_* functions
==========================

The new ser_* functions may perform reads faster than the UPS is able to
respond in some cases.  This means that your driver will call select()
and read() numerous times if your UPS responds in bursts.  This also
depends on how fast your system is.

You should check your driver with strace or its equivalent on your
system.  If the driver is calling read() multiple times, consider adding
a call to usleep before going into the ser_read_* call.  That will give
it a chance to accumulate so you get the whole thing with one call to
read without looping back for more.

This is not a request to save CPU time, even though it may do that.  The
important part here is making the strace/ktrace output easier to read.

	write(4, "Q1\r", 3)                     = 3
	nanosleep({0, 300000000}, NULL)         = 0
	select(5, [4], NULL, NULL, {3, 0})      = 1 (in [4], left {3, 0})
	read(4, "(120.0 084.0 120.0   0 60.0 22.6"..., 64) = 47

Without that delay, that turns into a mess of selects and reads.
The select returns almost instantly, and read gets a tiny chunk of the
data.  Add the delay and you get a nice four-line status poll.
