621 lines
22 KiB
Plaintext
621 lines
22 KiB
Plaintext
|
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>
|
||
|
Arnaud Quette <arnaud.quette@gmail.com>
|
||
|
|
||
|
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.
|
||
|
|
||
|
Serial vs. USB vs. SNMP and more
|
||
|
================================
|
||
|
|
||
|
If your UPS connects to your computer via a USB port, then go straight
|
||
|
to the document hid-subdrivers.txt. You can probably add support for
|
||
|
your device by writing a new subdriver to the existing usbhid-ups
|
||
|
driver, which is easier than writing an entire new driver.
|
||
|
|
||
|
Similarly, if your UPS connects to your computer via an SNMP network
|
||
|
card, you can probably add support for your device by writing a new
|
||
|
subdriver to the existing snmp-ups driver, which is easier than writing
|
||
|
an entire 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 structure
|
||
|
===================
|
||
|
|
||
|
upsdrv_info_t
|
||
|
-------------
|
||
|
|
||
|
This structure tracks several description information about the driver:
|
||
|
|
||
|
* name: the driver full name, for banner printing.
|
||
|
* version: the driver's own version. For sub driver information, refer below
|
||
|
to sub_upsdrv_info. This value has the form "X.YZ", and is
|
||
|
published by main as "driver.version.internal".
|
||
|
* authors: the driver's author(s) name. If multiple authors are listed, separate
|
||
|
them with '\n' so that it can be broken up by author if needed.
|
||
|
* status: the driver development status. The following values are allowed:
|
||
|
- DRV_BROKEN: setting this value 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.
|
||
|
- DRV_EXPERIMENTAL: set this value if your driver is potentially
|
||
|
broken. This will trigger a warning when it starts so the user
|
||
|
doesn't take it for granted.
|
||
|
- DRV_BETA: this value means that the driver is more stable and
|
||
|
complete. But it is still not recommended for production systems.
|
||
|
- DRV_STABLE: the driver is suitable for production systems, but not
|
||
|
100 % feature complete.
|
||
|
- DRV_COMPLETE: this is the gold level! It implies that 100 % of the
|
||
|
protocol is implemented, and a full QA pass.
|
||
|
* subdrv_info: array of upsdrv_info_t for sub driver(s) information. This
|
||
|
is used for example by usbhid-ups and megatec.
|
||
|
|
||
|
These information are currently used for the startup banner printing and tests.
|
||
|
|
||
|
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 the last time your driver is allowed to bail out.
|
||
|
|
||
|
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.
|
||
|
|
||
|
Don't spent more than a couple of seconds in this function. Typically
|
||
|
five (5) seconds is the maximum time allowed before you risk that the
|
||
|
server declares the driver stale. If your UPS hardware requires a
|
||
|
timeout period of several seconds before it answers, consider returning
|
||
|
from this function after sending a command immediately and read the
|
||
|
answer the next time it is called.
|
||
|
|
||
|
You must never abort from upsdrv_updateinfo(), even when the UPS doesn't
|
||
|
seem to be attached anymore. If the connection with the UPS is lost, the
|
||
|
driver should retry to re-establish communication for as long as it is
|
||
|
running. Calling exit() or any of the fatal*() functions is specifically
|
||
|
not allowed anymore.
|
||
|
|
||
|
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 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 (mains is present)
|
||
|
OB - On battery (mains is not present)
|
||
|
LB - Low battery
|
||
|
RB - The battery needs to be replaced
|
||
|
CHRG - The battery is charging
|
||
|
DISCHRG - The battery is discharging (inverter is providing load power)
|
||
|
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.
|
||
|
|
||
|
Note: the OL and OB flags are an indication of the input line status only.
|
||
|
|
||
|
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
|
||
|
|
||
|
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.
|
||
|
|
||
|
If you use UPS alarms, the call to status_commit() should be after
|
||
|
alarm_commit(), otherwise there will be a delay in setting the ALARM
|
||
|
flag in ups.status.
|
||
|
|
||
|
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
|
||
|
=================
|
||
|
|
||
|
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_dataok()
|
||
|
|
||
|
You must call this if polls are succeeding. A good place to call this
|
||
|
is the bottom of upsdrv_updateinfo().
|
||
|
|
||
|
- dstate_datastale()
|
||
|
|
||
|
You must call this if your status is unusable. A good technique is
|
||
|
to call this before exiting prematurely from upsdrv_updateinfo().
|
||
|
|
||
|
Don't hide calls to these functions deep inside helper functions. It is
|
||
|
very hard to find the origin of staleness warnings, if you call these from
|
||
|
various places in your code. Basically, don't call them from any other
|
||
|
function than from within upsdrv_updateinfo(). There is no need to call
|
||
|
either of these regularly as was stated in previous versions of this
|
||
|
document (that requirement has long gone).
|
||
|
|
||
|
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_set_dtr(int fd, int state)
|
||
|
- int ser_set_rts(int fd, int state)
|
||
|
|
||
|
These functions can be used to set the modem control lines to provide
|
||
|
cable power on the RS232 interface. Use state = 0 to set the line to 0
|
||
|
and any other value to set it to 1.
|
||
|
|
||
|
|
||
|
- int set_get_dsr(int fd)
|
||
|
- int ser_get_cts(int fd)
|
||
|
- int set_get_dcd(int fd)
|
||
|
|
||
|
These functions read the state of the modem control lines. They will
|
||
|
return 0 if the line is logic 0 and a non-zero value if the line is
|
||
|
logic 1.
|
||
|
|
||
|
|
||
|
- 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.
|
||
|
|
||
|
|
||
|
- 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.
|
||
|
|
||
|
|
||
|
- 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, -1
|
||
|
if something fails and 0 on 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(int fd, char *buf, size_t buflen, long d_sec,
|
||
|
long d_usec)
|
||
|
|
||
|
Like ser_get_char, but this one reads up to buflen bytes storing all of
|
||
|
them in buf. The buffer is zeroed regardless of success or failure. It
|
||
|
returns the number of bytes read, -1 on failure and 0 on a timeout.
|
||
|
|
||
|
This is essentially a single read() function with a timeout.
|
||
|
|
||
|
|
||
|
- int ser_get_buf_len(int fd, char *buf, size_t buflen, long d_sec,
|
||
|
long d_usec)
|
||
|
|
||
|
Like ser_get_buf, 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, -1 on failure
|
||
|
and 0 on 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. It returns the number of bytes stored in the buffer, -1 on
|
||
|
failure and 0 on a timeout.
|
||
|
|
||
|
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.
|
||
|
|
||
|
|
||
|
- int set_flush_io(int fd)
|
||
|
|
||
|
This function drains both the in- and output buffers. Return zero on
|
||
|
success.
|
||
|
|
||
|
|
||
|
- 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.
|
||
|
|
||
|
USB port handling
|
||
|
=================
|
||
|
|
||
|
Drivers which use USB functions should include usb-common.h and use these:
|
||
|
|
||
|
* structure and macro:
|
||
|
|
||
|
You should us the usb_device_id structure, and the USB_DEVICE macro to
|
||
|
declare the supported devices. This allows the automatic extraction of
|
||
|
USB information, to generate the HAL, Hotplug and udev support files.
|
||
|
|
||
|
For example:
|
||
|
/* SomeVendor name */
|
||
|
#define SOMEVENDOR_VENDORID 0xXXXX
|
||
|
|
||
|
/* USB IDs device table */
|
||
|
static usb_device_id sv_usb_device_table [] = {
|
||
|
/* some models 1 */
|
||
|
{ USB_DEVICE(SOMEVENDOR_VENDORID, 0xYYYY), NULL },
|
||
|
/* various models */
|
||
|
{ USB_DEVICE(SOMEVENDOR_VENDORID, 0xZZZZ), NULL },
|
||
|
{ USB_DEVICE(SOMEVENDOR_VENDORID, 0xAAAA), NULL },
|
||
|
/* Terminating entry */
|
||
|
{ -1, -1, NULL }
|
||
|
};
|
||
|
|
||
|
* function:
|
||
|
|
||
|
- is_usb_device_supported(usb_device_id **usb_device_id_list,
|
||
|
int dev_VendorID, int dev_ProductID)
|
||
|
|
||
|
Call this in your device opening / matching function. Pass your usb_device_id
|
||
|
structure, and a set of VendorID / DeviceID.
|
||
|
|
||
|
This function returns one of the following value:
|
||
|
NOT_SUPPORTED (0), POSSIBLY_SUPPORTED (1) or SUPPORTED (2)
|
||
|
POSSIBLY_SUPPORTED is returned when the VendorID is matched, but the DeviceID
|
||
|
is unknown.
|
||
|
|
||
|
For implementation examples, refer to the various USB drivers, and search for
|
||
|
the above patterns.
|
||
|
|
||
|
|
||
|
This set of USB helpers is due to expand is the near future...
|
||
|
|
||
|
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.
|
||
|
|
||
|
Canonical input mode processing
|
||
|
===============================
|
||
|
|
||
|
If your UPS uses '\n' and/or '\r' as endchar, consider the use of
|
||
|
Canonical Input Mode Processing instead of the ser_get_line* functions.
|
||
|
|
||
|
Using a serial port in this mode means that select() will wait until
|
||
|
a full line is received (or times out). This relieves you from waiting
|
||
|
between sending a command and reading the reply. Another benefit is,
|
||
|
that you no longer have to worry about the case that your UPS sends
|
||
|
"OK\n1234\nabcd\n". This will be broken up cleanly in "OK\n", "1234\n"
|
||
|
and "abcd\n" on consecutive reads, without risk of losing data (which
|
||
|
is an often forgotten side effect of the ser_get_line* functions).
|
||
|
|
||
|
Currently an example how this works can be found in the safenet and
|
||
|
upscode2 drivers. The first uses a single '\r' as endchar, while the
|
||
|
latter accepts either '\n', "\n\r" or "\r\n" as line termination. You
|
||
|
can define other termination characters as well, but can't undefine
|
||
|
'\r' and '\n' (so if you need these as data, this is not for you).
|