679 lines
25 KiB
Text
679 lines
25 KiB
Text
Creating a new driver to support another device
|
|
===============================================
|
|
|
|
This chapter will present the process to create a new driver to support
|
|
another device.
|
|
|
|
Since NUT already supports all major power devices protocols, through
|
|
several generic drivers (genericups, usbhid-ups, snmp-ups, blazer_*, ...),
|
|
creation of new drivers has become rare.
|
|
|
|
So most of the time, it will be limited to completing one of these
|
|
generic driver.
|
|
|
|
Smart vs. Contact-closure
|
|
-------------------------
|
|
|
|
If your UPS only does contact closure readings, then go straight to the
|
|
<<contact-closure, Contact closure hardware>> chapter 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 <<hid-subdrivers, HID subdrivers>> chapter. 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.
|
|
|
|
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 a newline character 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. For
|
|
example, this is used by usbhid-ups.
|
|
|
|
This information is 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 the nut-upsdev list 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 ser_get_dsr(int fd)
|
|
- int ser_get_cts(int fd)
|
|
- int ser_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, udev and DeviceKit-power
|
|
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, returned when the VendorID is matched, but the DeviceID is unknown),
|
|
- or SUPPORTED (2).
|
|
|
|
For implementation examples, refer to the various USB drivers, and search for
|
|
the above patterns.
|
|
|
|
NOTE: 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 the <<nut-names,NUT command and variable naming scheme>> section first to
|
|
find the closest fit. If nothing matches, contact the upsdev list, and we'll
|
|
figure it out.
|
|
|
|
Patches which introduce unlisted names may be modified or dropped.
|
|
|
|
[[commands]]
|
|
Message passing support
|
|
-----------------------
|
|
|
|
upsd can call drivers to store values in read/write variables and to kick
|
|
off instant commands. This is how you register handlers for those events.
|
|
|
|
The driver core (drivers/main.c) has a structure called upsh. You
|
|
should populate it with function pointers in your upsdrv_initinfo()
|
|
function. Right now, there are only two possibilities:
|
|
|
|
- setvar = setting UPS variables (SET VAR protocol command)
|
|
- instcmd = instant UPS commands (INSTCMD protocol command)
|
|
|
|
SET
|
|
~~~
|
|
|
|
If your driver's function for handling variable set events is called
|
|
my_ups_set(), then you'd do this to add the pointer:
|
|
|
|
upsh.setvar = my_ups_set;
|
|
|
|
my_ups_set() will receive two parameters:
|
|
|
|
const char * - the variable being changed
|
|
const char * - the new value
|
|
|
|
You should return either STAT_SET_HANDLED if your driver recognizes the
|
|
command, or STAT_SET_UNKNOWN if it doesn't. Other possibilities will be
|
|
added at some point in the future.
|
|
|
|
INSTCMD
|
|
~~~~~~~
|
|
|
|
This works just like the set process, with slightly different values
|
|
arriving from the server.
|
|
|
|
upsh.instcmd = my_ups_cmd;
|
|
|
|
Your function will receive two args:
|
|
|
|
const char * - the command name
|
|
const char * - (reserved)
|
|
|
|
You should return either STAT_INSTCMD_HANDLED or STAT_INSTCMD_UNKNOWN
|
|
depending on whether your driver can handle the requested command.
|
|
|
|
Notes
|
|
~~~~~
|
|
|
|
Use strcasecmp. The command names arriving from upsd should be treated
|
|
without regards to case.
|
|
|
|
Responses
|
|
~~~~~~~~~
|
|
|
|
Drivers will eventually be expected to send responses to commands.
|
|
Right now, there is no channel to get these back through upsd to
|
|
the client, so this is not implemented.
|
|
|
|
This will probably be implemented with a polling scheme in the clients.
|
|
|
|
|
|
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 (in bytes) 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).
|
|
|
|
|
|
[[contact-closure]]
|
|
|
|
include::contact-closure.txt[]
|
|
|
|
|
|
[[hid-subdrivers]]
|
|
|
|
include::hid-subdrivers.txt[]
|