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

4.1. Smart vs. Contact-closure

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.15. 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");

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

4.17. Instant commands

If your hardware and driver can support a command, register it.

dstate_addcmd("load.on");

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

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

4.20. Contact closure hardware information

This is a collection of notes that apply to contact closure UPS hardware, specifically those monitored by the genericups driver.

Definitions

"Contact closure" refers to a situation where one line is connected to another inside UPS hardware to indicate some sort of situation. These can be relays, or some other form of switching electronics. The generic idea is that you either have a signal on a line, or you don’t. Think binary.

Usually, the source for a signal is the host PC. It provides a high (logic level 1) from one of its outgoing lines, and the UPS returns it on one or more lines to communicate. The rest of the time, the UPS either lets it float or connects it to the ground to indicate a 0.

Other equipment generates the high and low signals internally, and does not require cable power. These signals just appear on the right lines without any special configuration on the PC side.

Bad levels

Some evil cabling and UPS equipment uses the transmit or receive lines as their reference points for these signals. This is not sufficient to register as a high signal on many serial ports. If you have problems reading certain signals on your system, make sure your UPS isn’t trying to do this.

Signals

Unlike their smarter cousins, this kind of UPS can only give you very simple yes/no answers. Due to the limited number of serial port lines that can be used for this purpose, you typically get two pieces of data:

  1. "On line" or "on battery"
  2. "Battery OK" or "Low battery"

That’s it. Some equipment actually swaps the second one for a notification about whether the battery needs to be replaced, which makes life interesting for those users.

Most hardware also supports an outgoing signal from the PC which means "shut down the load immediately". This is generally implemented in such a way that it only works when running on battery. Most hardware or cabling will ignore the shutdown signal when running on line power.

New genericups types

If none of the existing types in the genericups driver work completely, make a note of which ones (if any) manage to work partially. This can save you some work when creating support for your hardware.

Use that information to create a list of where the signals from your UPS appear on the serial port at the PC end, and whether they are active high or active low. You also need to know what outgoing lines, if any, need to be raised in order to supply power to the contacts. This is known as cable power. Finally, if your UPS can shut down the load, that line must also be identified.

There are only 4 incoming and 2 outgoing lines, so not many combinations are left. The other lines on a typical 9 pin port are transmit, receive, and the ground. Anything trying to do a high/low signal on those three is beyond the scope of the genericups driver. The only exception is an outgoing BREAK, which we already support.

When editing the genericups.h, the values have the following meanings:

Outgoing lines:

  • line_norm = what to set to make the line "normal" - i.e. cable power
  • line_sd = what to set to make the UPS shut down the load

Incoming lines:

  • line_ol = flag that appears for on line / on battery
  • val_ol = value of that flag when the UPS is on battery
  • line_bl = flag that appears for low battery / battery OK
  • val_bl = value of that flag when the battery is low

This may seem a bit confusing to have two variables per value that we want to read, but here’s how it works. If you set line_ol to TIOCM_RNG, then the value of TIOCM_RNG (0x080 on my box) will be anded with the value of the serial port whenever a poll occurs. If that flag exists, then the result of the and will be 0x80. If it does not exist, the result will be 0.

So, if line_ol = foo, then val_ol can only be foo or 0.

As a general case, if line_ol == val_ol, then the value you’re reading is active high. Otherwise, it’s active low. Check out the guts of upsdrv_updateinfo() to see how it really works.

Custom definitions

Late in the 1.3 cycle, a feature was merged which allows you to create custom monitoring settings without editing the model table. Just set upstype to something close, then use settings in ups.conf to adjust the rest. See the genericups(8) man page for more details.

4.21. How to make a new subdriver to support another USB/HID UPS

Overall concept

USB (Universal Serial Port) devices can be divided into several different classes (audio, imaging, mass storage etc). Almost all UPS devices belong to the "HID" class, which means "Human Interface Device", and also includes things like keyboards and mice. What HID devices have in common is a particular (and very flexible) interface for reading and writing information (such as x/y coordinates and button states, in case of a mouse, or voltages and status information, in case of a UPS).

The NUT "usbhid-ups" driver is a meta-driver that handles all HID UPS devices. It consists of a core driver that handles most of the work of talking to the USB hardware, and several sub-drivers to handle specific UPS manufacturers (MGE, APC, and Belkin are currently supported). Adding support for a new HID UPS device is easy, because it requires only the creation of a new sub-driver.

There are a few USB UPS devices that are not HID devices. These devices typically implement some version of the manufacturer’s serial protocol over USB (which is a really dumb idea, by the way). An example is the Tripplite USB. Such devices are not supported by the usbhid-ups driver, and are not covered in this document. If you need to add support for such a device, read new-drivers.txt and see the tripplite_usb driver for inspiration.

HID Usage Tree

From the point of view of writing a HID subdriver, a HID device consists of a bunch of variables. Some variables (such as the current input voltage) are read-only, whereas other variables (such as the beeper enabled/disabled/muted status) can be read and written. These variables are usually grouped together and arranged in a hierarchical tree shape, similar to directories in a file system. This tree is called the "usage" tree. For example, here is part of the usage tree for a typical APC device. Variable components are separated by ".". Typical values for each variable are also shown for illustrative purposes.

UPS.Battery.Voltage

11.4 V

UPS.Battery.ConfigVoltage

12 V

UPS.Input.Voltage

117 V

UPS.Input.ConfigVoltage

120 V

UPS.AudibleAlarmControl

2 (=enabled)

UPS.PresentStatus.Charging

1 (=yes)

UPS.PresentStatus.Discharging

0 (=no)

UPS.PresentStatus.ACPresent

1 (=yes)

As you can see, variables that describe the battery status might be grouped together under "Battery", variables that describe the input power might be grouped together under "Input", and variables that describe the current UPS status might be grouped together under "PresentStatus". All of these variables are grouped together under "UPS".

This hierarchical organization of data has the advantage of being very flexible; for example, if some device has more than one battery, then similar information about each battery could be grouped under "Battery1", "Battery2" and so forth. If your UPS can also be used as a toaster, then information about the toaster function might be grouped under "Toaster", rather than "UPS".

However, the disadvantage is that each manufacturer will have their own idea about how the usage tree should be organized, and usbhid-ups needs to know about all of them. This is why manufacturer specific subdrivers are needed.

To make matters more complicated, usage tree components (such as "UPS", "Battery", or "Voltage") are internally represented not as strings, but as numbers (called "usages" in HID terminology). These numbers are defined in the "HID Usage Tables", available from http://www.usb.org/developers/hidpage/. The standard usages for UPS devices are defined in a document called "Usage Tables for HID Power Devices" (the Power Device Class [PDC] specification).

For example:

 0x00840010 = UPS
 0x00840012 = Battery
 0x00840030 = Voltage
 0x00840040 = ConfigVoltage
 0x0084001a = Input
 0x0084005a = AudibleAlarmControl
 0x00840002 = PresentStatus
 0x00850044 = Charging
 0x00850045 = Discharging
 0x008500d0 = ACPresent

Thus, the above usage tree is internally represented as:

 00840010.00840012.00840030
 00840010.00840012.00840040
 00840010.0084001a.00840030
 00840010.0084001a.00840040
 00840010.0084005a
 00840010.00840002.00850044
 00840010.00840002.00850045
 00840010.00840002.008500d0

To make matters worse, most manufacturers define their own additional usages, even in cases where standard usages could have been used. for example Belkin defines 00860040 = ConfigVoltage (which is incidentally a violation of the USB PDC specification, as 00860040 is reserved for future use).

Thus, subdrivers generally need to provide:

  • manufacturer-specific usage definitions,
  • a mapping of HID variables to NUT variables.

Moreover, subdrivers might have to provide additional functionality, such as custom implementations of specific instant commands (load.off, shutdown.restart), and conversions of manufacturer specific data formats.

Writing a subdriver

In preparation for writing a subdriver for a device that is currently unsupported, run usbhid-ups with the following command line:

drivers/usbhid-ups -DD -u root -x explore -x vendorid=XXXX auto

(substitute your device’s 4-digit VendorID instead of "XXXX"). This will produce a bunch of debugging information, including a number of lines starting with "Path:" that describe the device’s usage tree. This information forms the initial basis for a new subdriver.

You should save this information to a file, e.g. drivers/usbhid-ups -DD -u root -x explore -x vendorid=XXXX auto >& /tmp/info

You can create an initial "stub" subdriver for your device by using script scripts/subdriver/path-to-subdriver.sh. Note: this only creates a "stub" and needs to be futher customized to be useful (see CUSTOMIZATION below).

Use the script as follows:

scripts/subdriver/path-to-subdriver.sh < /tmp/info

where /tmp/info is the file where you previously saved the debugging information.

This script prompts you for a name for the subdriver; use only letters and digits, and use natural capitalization such as "Belkin" (not "belkin" or "BELKIN"). The script may prompt you for additional information.

You should put the generated files into the drivers/ subdirectory, and update usbhid-ups.c by adding the appropriate #include line and by updating the definition of subdriver_list in usbhid-ups.c. You must also add the subdriver to USBHID_UPS_SUBDRIVERS in drivers/Makefile.am and call "autoreconf" and/or "./configure" from the top level NUT directory. You can then recompile usbhid-ups, and start experimenting with the new subdriver.

CUSTOMIZATION: The initially generated subdriver code is only a stub, and will not implement any useful functionality (in particular, it will be unable to shut down the UPS). In the beginning, it simply attempts to monitor some UPS variables. To make this driver useful, you must examine the NUT variables of the form "unmapped.*" in the hid_info_t data structure, and map them to actual NUT variables and instant commands. There are currently no step-by-step instructions for how to do this. Please look at the files to see how the currently implemented subdrivers are written.:

  • apc-hid.c/h
  • belkin-hid.c/h
  • cps-hid.c/h
  • explore-hid.c/h
  • libhid.c/h
  • liebert-hid.c/h
  • mge-hid.c/h
  • powercom-hid.c/h
  • tripplite-hid.c/h

Shutting down the UPS

It is desireable to support shutting down the UPS. Usually (for devices that follow the HID Power Device Class specification), this requires sending the UPS two commands. One for shutting down the UPS (with an offdelay) and one for restarting it (with an ondelay), where offdelay < ondelay. The two NUT commands for which this is relevant, are shutdown.return and shutdown.stayoff.

Since the one-to-one mapping above doesn’t allow sending two HID commands to the UPS in response to sending one NUT command to the driver, this is handled by the driver. In order to make this work, you need to define the following four NUT values:

ups.delay.start    (variable, R/W)
ups.delay.shutdown (variable, R/W)
load.off.delay     (command)
load.on.delay      (command)

If the UPS supports it, the following variables can be used to show the countdown to start/shutdown:

ups.timer.start    (variable, R/O)
ups.timer.shutdown (variable, R/O)

The load.on and load.off commands will be defined implicitly by the driver (using a delay value of 0). Define these commands yourself, if your UPS requires a different value to switch on/off the load without delay.

Note that the driver expects the load.off.delay and load.on.delay to follow the HID Power Device Class specification, which means that the load.on.delay command should NOT switch on the load in the absence of mains power. If your UPS switches on the load regardless of the mains status, DO NOT define this command. You probably want to define the shutdown.return and/or shutdown.stayoff commands in that case. Commands defined in the subdriver will take precedence over the ones that are composed in the driver.

When running the driver with the -k flag, it will first attempt to send a shutdown.return command and if that fails, will fallback to shutdown.reboot.