Prev |  | Next

## 5. Creating a new driver to support another device

This chapter will present the process of creating a new driver to support another device.

Since NUT already supports many major power device protocols through several generic drivers (`genericups`, `usbhid-ups`, `snmp-ups`, `blazer_*` and `nutdrv_qx`), creation of new drivers has become rare.

### Note

We have yet to unify modbus drivers under same umbrella like `nutdrv_qx` covering the Megatec Qx protocol family.

So most of the time, this process will be limited to completing one of these generic drivers.

### 5.1. Smart vs. Contact-closure

If your UPS only does contact closure readings over an RS-232 serial port, 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.

### 5.2. Serial vs. USB vs. SNMP and more

If your UPS connects to your computer via a USB port, then it most likely appears as a USB HID device (this is the simplest way for the vendor to write a Windows control program for it). What comes next depends on whether the vendor implemented the HID PDC (Power Device Class) specification, or simply used the HID protocol to transport serial data to the UPS microcontroller.

A rough heuristic is to check the length of the HID Descriptor length (`wDescriptorLength` in `lsusb -v` output). If it is less than 200 bytes long, the UPS probably has a glorified USB-to-serial converter built in. Since the query strings often start with the letter `Q`, this family of protocols is often referred to as `Q*` in the NUT documentation. See the Q* UPS chapter for more details.

Otherwise, if the HID Descriptor is longer, you can go 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 (and more maintainable) than writing an entire new driver.

If your USB UPS does not appear to fall into either of these two categories, feel free to contact the `nut-upsdev` mailing list with details of your device.

Similarly, if your UPS connects to your computer via an SNMP network card, you can probably add support for your device by adding a new subdriver to the existing snmp-ups driver. Instructions are provided in the SNMP subdrivers chapter.

### 5.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.

### 5.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.c` will call to obtain updated information from the hardware.

### 5.5. Essential structure

#### upsdrv_info_t

This structure tracks several description information about the driver:

- name: the driver full name, for banner printing and "driver.name" variable.
- 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.

### 5.6. Essential functions

For a full current list of functions expected in this context, please see the `drivers/main.h` file in NUT sources. Some more methods may be required by driver structure, even if in the common case they have no-op implementations.

#### 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`, determine and declare supported instant commands (maybe model-dependent, typically for all devices supported by the driver), 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.

This method should not directly `exit()` the driver program (neither should it call `fatalx()` nor `fatal_with_errno()` methods). It can `upslogx(LOG_ERR, ...)` or `upslog_with_errno(LOG_ERR, ...)`, and then `set_exit_flag(N)` if required, using values `EF_EXIT_FAILURE` (`-1`) for eventual `exit(EXIT_FAILURE)` and `EF_EXIT_SUCCESS` (`-2`) for `exit(EXIT_SUCCESS)`, which would be handled in the standard driver loop or in `forceshutdown()` method of `main.c`.

#### addvar, getval and testvar

NUT drivers can be configured at run-time with optional settings, which can be passed on command line or via the ups.conf(5) file. Such settings broadly fall into two categories: "values" which convey some specific value (e.g. `port` or `desc` strings), and "flags" which can be seen as a sort of booleans which are `true` if mentioned and `false` if not (any assigned value would be ignored, if not rejected by configuration parser).

Some options are common to all drivers and are handled in `drivers/main.c` (which also defines and implements most of the methods in this area), while others may be specific to individual drivers or even their sub-drivers.

The `addvar()` method allows to specify a certain option name, with its type and a help message string:

    addvar(VAR_VALUE, "sdtype", "Specify simple shutdown method (0-6)");

    addvar(VAR_FLAG,  "wait",   "Wait for AC power");

In driver code, these calls should comprise the `upsdrv_makevartable()` method.

The `getval()` method allows to get the user-provided string value (or `NULL`— but it is hard to tell apart if the option was not specified or no value was assigned). The `testvar()` method allows to check if the option name was specified (even if without a value), so is better suited for flags. Internally there is no difference between "values" and "flags", so historically some flags were "promoted" to values with boolean-ish strings handled explicitly, in addition to some new features.

### Note

Internally, the values can also be marked as reloadable (so that a running driver program may change them when re-reading the configuration file due to a signal). The implementation may be currently limited to common options handled by `main.c` itself. In code base this is exposed with the `addvar_reloadable()` and several `test*_reloadable()` methods.

Since these methods involve a data structure walk to locate (or not) the entry for each option, they should not be used in data processing loops. Instead, the driver initialization methods such as `upsdrv_initups()` should consider all values and flags that can impact the set-up of the driver, and save them into C variables (typically strings, booleans, or processed and range/value-checked number types) which are then directly used in the driver program code.

#### nut_usb_addvars

For consistency between USB-capable NUT drivers, a set of `addvar()` options related to USB media setup and expected by `nut_libusb_open()` can be defined by calling the `nut_usb_addvars()` method, which can be seen in `drivers/libusb{0,1}.c`.

Like `addvar()`, a call to this method belongs in `upsdrv_makevartable()` (see existing drivers for examples).

#### nut_ser_addvars

### Warning

NOT CURRENTLY IMPLEMENTED.

Issue https://github.com/networkupstools/nut/issues/2748 tracks the idea to standardize the set and names of options involved in the serial driver connection set-up (speed, parity, stop-bits, etc.) similarly to the precedent of `nut_usb_addvars()`. One problem is that many drivers released during the past decades reinvented this wheel with different option names.

#### nut_modbus_addvars

### Warning

NOT CURRENTLY IMPLEMENTED.

Issue https://github.com/networkupstools/nut/issues/2419 tracks a similar idea for Modbus-capable drivers (which may involve or not Serial and/or USB link and option support, and uniquely TCP/IP RTU support, for example).

### 5.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.

### 5.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);

In a few cases you can use a formatting string coming from a mapping table or constructed during run-time. This is generally not safe (due to references into the stack when handling the variable argument list), and modern compilers warn against doing so. While it is possible to quiesce the warnings with pragmas, it is better to play safe with the "dynamic" versions of methods provided by NUT — they allow to combine both compile-time checks of expected formatting string vs. types of data in the method arguments, and run-time equivalence of the actual dynamic formatting string to those expectations. In this case, you would use `dstate_setinfo_dynamic()` with a coding pattern similar to the following:

    char *fmt = "Mega-Zapper %d";
    dstate_setinfo_dynamic("ups.model", fmt, "%d", rating);

Please note that `ups.alarm` should no longer be manually set, but rather the appropriate alarm functions should be used instead. For more details, see below in the `UPS alarms` section.

#### 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 (clear internal buffers,
                       etc.)

    status_get(val) -- optionally check if a status word had been set
                       since the most-recent status_init()

    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
    HB      -- High 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
    FSD     -- Forced Shutdown (restricted use, see the note below)

`ALARM` should no longer be raised through the UPS status. An `ALARM` value will be added internally (typically as first in the list) if an alarm was set and committed through the appropriate alarm functions. For more details, see below in the `UPS alarms` section.

### Note

The NUT data server `upsd` initially sets `ups.status` to a value of `WAIT` when first connecting to a NUT driver using the socket protocol, and issues a `DUMPALL` command. This temporary `WAIT` status gets overwritten whenever any relevant information update from a driver arrives, typically either as part of its own initialization, or of the regular polling loop.

This may seem roughly similar to a "Data stale" situation; however, the NUT clients like `upsmon` should not infer anything for shutdown decisions from this lack of initial connection, like they do for loss of connection during a known critical power situation.

For some more detailed insight into the NUT driver’s current state machine position (as reported by the driver after communications are established), see also `driver.state`.

Anything else will not be recognized by the usual clients expecting a particular NUT standard release. New tokens may appear over time, but driver developers should coordinate with the nut-upsdev list before creating something new, since there will be duplication and ugliness otherwise. It is possible that eventually, due to hardware and software design evolution, some concepts would be superseded by others. Fundamental meanings of the flags listed above should not change (but these flags may become no longer issued by the current NUT drivers; then may still be used e.g. in forks or older packaged builds).

Clients however MUST accept any space-separated tokens in `ups.status` without error or crash, and MUST treat those defined above with the ascribed meanings, but MAY ignore unidentified tokens (quietly by default, or acknowledge the skip with a debug log message).

### Note

- upsd injects `FSD` by itself following that command by a primary upsmon process. Drivers must not set that value, apart from specific cases (see below).
- As an exception, drivers may set `FSD` when an imminent shutdown has been detected. In this case, the "on battery + low battery" condition should not be met. Otherwise, setting status to `OB LB` should be preferred.
- the `OL` and `OB` flags are an indication of the input line status only.
- the `CHRG` and `DISCHRG` flags are being replaced with `battery.charger.status`. See the NUT command and variable naming scheme for more information.

Similar functionality can be supported for `experimental.ups.mode.buzzwords`, where it is tracked dynamically (e.g. due to ECO/ESS/HE/Smart or similar marketing buzzword modes supported by the device), using the following methods in the processing loop:

    buzzmode_init()   -- before doing anything else (clear internal buffers,
                         etc.)

    buzzmode_get(val) -- optionally check if an UPS mode buzzword had been
                         set since the most-recent buzzmode_init()

    buzzmode_set(val) -- add an UPS mode buzzword (vendor:eaton:ECO, etc.)

    buzzmode_commit() -- push out the update

### 5.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 keywords as of this writing, so don’t use these functions until you check with the upsdev list.

Also refer to the NUT daisychain support notes chapter of the user manual and developer guide for information related to alarms handling in daisychain mode.

### 5.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).

### 5.11. Serial port handling

Drivers which use serial port functions should include serial.h and use these functions (and cross-platform data types) whenever possible:

- TYPE_FD

  Cross-platform data type to represent a serial-port connection.

- ERROR_FD_SER

  Macro value representing an invalid serial-port connection.

- VALID_FD_SER(TYPE_FD_SER fd)

  This macro evaluates to `true` if `fd` currently has a "valid" value (e.g. represents a connected device). You should invalidate the `fd` when you initialize the variable or close the connection, by assigning `fd = ERROR_FD`.

- INVALID_FD_SER(TYPE_FD_SER fd)

  This macro evaluates to `true` if `fd` does not currently have a "valid" value.

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

- TYPE_FD_SER ser_open_nf(const char *port)

  This is a non-fatal version of ser_open(), that does not call fatal if something fails.

- int ser_set_speed(TYPE_FD_SER 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_speed_nf(TYPE_FD_SER fd, const char *port, speed_t speed)

  This is a non-fatal version of ser_set_speed(), that does not call fatal if something fails.

- int ser_set_dtr(TYPE_FD_SER fd, int state)

- int ser_set_rts(TYPE_FD_SER 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(TYPE_FD_SER fd)
- int ser_get_cts(TYPE_FD_SER fd)

- int ser_get_dcd(TYPE_FD_SER 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(TYPE_FD_SER fd, const char *port)

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

- ssize_t ser_send_char(TYPE_FD_SER fd, unsigned 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.

- ssize_t ser_send_pace(TYPE_FD_SER fd, useconds_t 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.

- ssize_t ser_send(TYPE_FD_SER 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.

- ssize_t ser_send_buf(TYPE_FD_SER fd, const void *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.

- ssize_t ser_send_buf_pace(TYPE_FD_SER fd, useconds_t d_usec, const void *buf, size_t buflen)

  This is just ser_send_buf with an intercharacter delay.

- ssize_t ser_get_char(TYPE_FD_SER fd, void *ch, time_t d_sec, useconds_t 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.

- ssize_t ser_get_buf(TYPE_FD_SER fd, void *buf, size_t buflen, time_t d_sec, useconds_t 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.

- ssize_t ser_get_buf_len(TYPE_FD_SER fd, void *buf, size_t buflen, time_t d_sec, useconds_t 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.

- ssize_t ser_get_line(TYPE_FD_SER fd, void *buf, size_t buflen, char endchar, const char *ignset, time_t d_sec, useconds_t 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.

- ssize_t ser_get_line_alert(TYPE_FD_SER fd, void *buf, size_t buflen, char endchar, const char *ignset, const char *alertset, void handler(char ch), time_t d_sec, useconds_t 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.

- ssize_t ser_flush_in(TYPE_FD_SER 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 ser_flush_io(TYPE_FD_SER 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.

### 5.12. USB port handling

Drivers which use USB functions should include usb-common.h and use these:

#### Structure and macro

You should use the usb_device_id_t structure, and the USB_DEVICE macro to declare the supported devices. This allows the automatic extraction of USB information, to generate the Hotplug, udev and UPower support files.

The structure allows to convey `uint16_t` values of VendorID and ProductID, and an optional matching-function callback to interrogate the device in more detail (constrain known supported firmware versions, OEM brands, etc.)

For example:

    /* SomeVendor name */
    #define SOMEVENDOR_VENDORID             0xXXXX

    /* USB IDs device table */
    static usb_device_id_t 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 */
            { 0, 0, NULL }
    };

#### Function

- is_usb_device_supported(usb_device_id_t *usb_device_id_list, USBDevice_t *device)

  Call this in your device opening / matching function. Pass your usb_device_id_t list structure, and a set of VendorID, DeviceID, as well as Vendor, Product and Serial strings, possibly also Bus, bcdDevice (device release number) and Device name on the bus strings, in the USBDevice_t fields describing the specific piece of hardware you are inspecting.

  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…

### 5.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.

### 5.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.

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

### 5.16. Range values

If you have a variable that support values comprised in one or more ranges, you should add each one to make it available to the client:

    dstate_addrange("input.transfer.low", 90, 95);
    dstate_addrange("input.transfer.low", 100, 105);

### 5.17. 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.

### 5.18. Instant commands

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

    dstate_addcmd("load.on");

Don’t forget to define the implementation for such commands in a common method, and register that your driver has an instant command handler at all — with a line in `upsdrv_initinfo()` like:

    upsh.instcmd = blazer_instcmd;

### 5.19. 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.

### 5.20. 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).

### 5.21. Adding the driver into the tree

In order to build your new driver, it needs to be added to `drivers/Makefile.am`. At the moment, there are several driver list variables corresponding to the general protocol of the driver (`SERIAL_DRIVERLIST`, `SNMP_DRIVERLIST`, etc.). If your driver does not fit into one of these categories, please discuss it on the nut-upsdev mailing list.

There are also `*_SOURCES` and optional `*_LDADD` variables to list the source files, and any additional linker flags. If your driver uses the C math library, be sure to add `-lm`, since this flag is not always included by default on embedded systems.

When you add a driver to one of these lists, pay attention to the backslash continuation characters (`\\`) at the end of the lines.

The `automake` program converts the `Makefile.am` files into `Makefile.in` files to be processed by `./configure`. See the discussion in Section 4.16, “Building the Code” about automating the rebuild process for these files.

### 5.22. 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
- line_rb = flag that appears for battery health
- val_rb = value of that flag when the battery needs a replacement
- line_bypass = flag that appears for battery bypass / battery protection active
- val_bypass = value of that flag when the battery is bypassed / missing

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.

### 5.23. 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 the case of a mouse, or voltages and status information, in the 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 true 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 original Tripplite USB interface (USB idProduct = 0001). Its HID descriptor is only 52 bytes long (compared to several hundred bytes for a true PDC HID UPS). 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.

#### Usage macros in drivers/hidtypes.h

The `drivers/hidtypes.h` header provides a number of macro names for entries in the standard usage tables for Power Device `USAGE_POW_<SOMETHING>` and Battery System `USAGE_BAT_<SOMETHING>` data pages.

If NUT codebase would ever need to refresh those macros, here is some background information (based on NUT issue #1189 and PR #1290):

These data were parsed from (a very slightly updated version of) https://github.com/abend0c1/hidrdd/blob/master/rd.conf file, which incorporates the complete USB-IF usage definitions for Power Device and Battery System pages (among many others), so we didn’t have to extract the names and values from the USB-IF standards documents (did check it all by eye though).

The file was processed with the following chain of commands:

    :; grep -e '^0084' -e '^0085' rd.conf \
       | sed 's/,.*$//;s/ *$//' \
       | sed 's/ /_/g;s/_/ /' \
       | tr '[:lower:]' '[:upper:]' \
       | sed 's/\(0085.... \)/\1USAGE_BAT_/;s/\(0084.... \)/\1USAGE_POW_/;s/\([A-Z_]*\)_PAGE/PAGE_\1/' \
       | awk '{print "#define "$2" 0x"$1}'

#### 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 \
            -x port=auto -s ups

(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 \
            -x port=auto -s ups -d1 2>&1 | tee /tmp/info

You can now create an initial "stub" subdriver for your device by using helper script `scripts/subdriver/gen-usbhid-subdriver.sh`.

### Note

This only creates a driver code "stub" which needs to be further customized to be actually useful (see "Customization" below).

Use the script as follows:

    scripts/subdriver/gen-usbhid-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.

#### Updating a subdriver

You may have a device from vendor (and maybe model) whose support `usbhid-ups` already claims. However, you may feel that the driver does not represent all data points that your device serves. This may be possible, as vendors tend to use the same identifiers for unrelated products, as well as produce revisions of devices with same marketed name but different internals (due to chip and other components availability, cost optimization, etc.) Even without sinister implications, UPS firmwares evolve and so bugs and features can get added, fixed and removed over time with truly the same hardware being involved.

In this case you should follow the same instructions as above for "Writing a subdriver", but specify the same subdriver name as the one which supports your device family already.

Then compare the generated source file with the one already committed to NUT codebase, paying close attention to `..._hid2nut[]` table which maps "usage" names to NUT data points. There may be several "usage" values served by different device models or firmware versions, that provide same information for a NUT data point, such as `input.voltage`. For the `hid2nut` mapping tables, first hit wins (so you may e.g. prefer to check values with better precision first).

Using a GUI tool with partial-line difference matching and highlighting, such as Meld or WinMerge, is recommended for this endeavour.

Alternatively, since NUT v2.8.5, the `usbhid-ups` driver initialization debug log should clarify which values were parsed from the device report descriptor, but were not looked at when walking the subdriver mapping table. Some of these items may be due to the device reporting same HID Path with different Type values (e.g. as an Input and as a Feature) with the driver only using one of those; in other cases truly new mappings can be discovered.

For new data points in `hid2nut` tables be sure to not invent new names, but use standard ones from `docs/nut-names.txt` file. Temporarily, the `experimental.*` or `unmapped.*` namespaces may be used. If you need to standardize a name for some concept not addressed yet, please do so via nut-upsdev mailing list discussion.

### Note

For new devices (or firmwares) please also anticipate that a wrong subdriver can get used by default, and a better one may already exist (perhaps because a vendor matched in the older NUT code by name introduced new device models with different dialects) — in this case, please focus on `claim` methods to ensure that the new devices get handled by the better 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

### Note

To test existing data points (including those not yet translated to standard NUT mappings conforming to NUT command and variable naming scheme), you can use custom drivers built after you `./configure --with-unmapped-data-points`. Production driver builds must not include any non-standard names.

#### Fixing report descriptors

It is a fact of life that fellow developers make mistakes, and firmware authors do too. In some cases there are inconsistencies about bytes seen on the wire vs. their logical values, such value range and signedness if interpreting them according to standard.

NUT drivers now include a way to detect and fix up known issues in such flawed USB report descriptors, side-stepping the standard similarly where deemed needed. A pointer to such hook method is part of the `subdriver_t` structure detailing each `usbhid-ups` subdriver nuances, defaulting to a `fix_report_desc()` trivial implementation.

For some practical examples, see e.g. `apc_fix_report_desc()` method in the `drivers/apc-hid.c` file, and `cps_fix_report_desc()` in `drivers/cps-hid.c` file.

Finally note that such fix-ups may be not applicable to all devices or firmware versions for what they assume their target audience is. If you suspect that the fix-up method is actually causing problems, you can quickly disable it with `disable_fix_report_desc` driver option for `usbhid-ups`. If the problem does dissipate, please find a way to identify your "fixed" hardware/firmware vs. those models where existing fix-up method should be applied, and post a pull request so the NUT driver would handle both cases.

#### Investigating report descriptors

Beside looking for problems with report descriptor processing in NUT code, it is important to make sure what data the device actually serves on the wire, and if it is logically consistent with the protocol requirements.

While here, keep in mind that USB protocol on the wire has a specified order of bytes involved, while processing on your computer may lay them out differently due to bitness and endianness of the current binary build. General NUT codebase (`libhid.c`, `hidparser.c`) aims to abstract this, so application code like drivers can deal with their native numeric data types, but when troubleshooting, do not rule out possibility of flaws there as well. And certainly do not code any assumptions about ordered multiple-byte ranges in a protocol buffer.

For a deep dive into the byte stream, you will need additional tools:

- get/build/install regina-rexx
- get/install HIDRDD (uses REXX as the interpreter)

Typical troubleshooting of suspected firmware/protocol issues goes like this:

- Turn the NUT `usbhid-ups` driver debug verbosity level up to 5 (or more) and restart the driver, so it would record the HEX dump of report descriptor
- Look for reports from the driver of any problems it has already detected and possibly amended (LogMin/LogMax, report descriptor fix-ups)
- Extract the HEX dump of the report descriptor from USB driver output from the first step above, and run it through HIDRDD (and/or REXX directly, per example below).
- Look at the HIDRDD output, with reference to any documents related to your device and the USB/HID power devices class available in NUT documentation, e.g. at https://www.networkupstools.org/ups-protocols.html
- Especially look for inconsistencies in the USB HID report descriptors (RD):
- between the min/max (logical and physical) values,
- the sizes of the report fields they apply to,
- the expected physical values (e.g., supply and output voltages, over-voltage/under-voltage transfer points, …)
- If you’re seeing unexpected values for particular variables, look at the raw data that is being sent, decide whether it makes sense in the context of the logical and physical min/max values from the report descriptor.
- Read the NUT code, tracing through how each value gets processed looking for where the result deviates from expectations…
- Think, code, test, rinse, repeat, post a PR :)

Example 1. Example direct use of REXX

Example adapted from https://github.com/networkupstools/nut/issues/2039

Run a NUT `usb-hid` driver with at least debug verbosity level 3 (`-DDD`) to get a report descriptor dump starting with a line like this:

       3.670755     [D3] Report Descriptor: (909 bytes) => 05 84 09 04 a1 01 ...

…and copy-paste those reported lines as input into `rexx` tool, which would generate a C source file including human-worded description and a relevant data structure:

    :; rexx rd.rex -d --hex 05 84 09 04 a1 01 85 01 09 18 ... 55 b1 02 c0 c0 c0

    //--------------------------------------------------------------------------------
    // Decoded Application Collection
    //--------------------------------------------------------------------------------

    /*
    05 84        (GLOBAL) USAGE_PAGE         0x0084 Power Device Page
    09 04        (LOCAL)  USAGE              0x00840004 UPS (Application Collection)
    A1 01        (MAIN)   COLLECTION         0x01 Application (Usage=0x00840004: Page=Power Device Page, Usage=UPS, Type=Application Collection)
    85 01          (GLOBAL) REPORT_ID          0x01 (1)
    09 18          (LOCAL)  USAGE              0x00840018 Outlet System (Physical Collection)
    ...
    */

    // All structure fields should be byte-aligned...
    #pragma pack(push,1)

    //--------------------------------------------------------------------------------
    // Power Device Page featureReport 01 (Device <-> Host)
    //--------------------------------------------------------------------------------

    typedef struct
    {
      uint8_t  reportId;                                 // Report ID = 0x01 (1)
                                                         // Collection: CA:UPS CP:OutletSystem CP:Outlet
      int8_t   POW_UPSOutletSystemOutletSwitchable;      // Usage 0x0084006C: Switchable, Value =  to
      int8_t   POW_UPSOutletSystemOutletDelayBeforeStartup; // Usage 0x00840056: Delay Before Startup, Value = -1 to 60
      int8_t   POW_UPSOutletSystemOutletDelayBeforeShutdown; // Usage 0x00840057: Delay Before Shutdown, Value = -1 to 60
      int8_t   POW_UPSOutletSystemOutletDelayBeforeReboot; // Usage 0x00840055: Delay Before Reboot, Value = -1 to 60
      int8_t   POW_UPSOutletSystemOutletSwitchable_1;    // Usage 0x0084006C: Switchable, Value = -1 to 60
      int8_t   POW_UPSOutletSystemOutletDelayBeforeStartup_1; // Usage 0x00840056: Delay Before Startup, Value = -1 to 60
      int8_t   POW_UPSOutletSystemOutletDelayBeforeShutdown_1; // Usage 0x00840057: Delay Before Shutdown, Value = -1 to 60
      int8_t   POW_UPSOutletSystemOutletDelayBeforeReboot_1; // Usage 0x00840055: Delay Before Reboot, Value = -1 to 60
    } featureReport01_t;

    #pragma pack(pop)

#### Shutting down the UPS

It is desirable 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`.

### 5.24. How to make a new subdriver to support another SNMP device

#### Overall concept

The SNMP protocol allow for a common way to interact with devices over the network.

The NUT "snmp-ups" driver is a meta-driver that handles many SNMP devices, such as UPS and PDU. It consists of a core driver that handles most of the work of talking to the SNMP agent, and several sub-drivers to handle specific device manufacturers. Adding support for a new SNMP device is easy, because it requires only the creation of a new sub-driver.

#### SNMP data Tree

From the point of view of writing an SNMP subdriver, an SNMP device consists of a bunch of variables, called OIDs (for Object IDentifiers). Some OIDs (such as the current input voltage) are read-only, whereas others (such as the beeper enabled/disabled/muted status) can be read and written. OID are grouped together and arranged in a hierarchical tree shape, similar to directories in a file system. OID components are separated by ".", and can be expressed in numeric or textual form. For example:

    .iso.org.dod.internet.mgmt.mib-2.system.sysObjectID

is equivalent to:

    .1.3.6.1.2.1.1.2.0

Here is an excerpt tree, showing only two OIDs, sysDescr and sysObjectID:

    .iso
            .org
                    .dod
                            .internet
                                    .mgmt
                                            .mib-2
                                                    .system
                                                            .sysDescr.0 = STRING: Dell UPS Tower 1920W HV
                                                            .sysObjectID.0 = OID: .iso.org.dod.internet.private.enterprises.674.10902.2
                                                            (...)
                                                    .upsMIB
                                                            .upsObjects
                                                                    .upsIdent
                                                                            .upsIdentModel = STRING: "Dell UPS Tower 1920W HV"
                                                                            (...)
                                    .private
                                            .enterprises
                                                    .674
                                                            .10902
                                                                    .2
                                                                            .100
                                                                                    .1.0 = STRING: "Dell UPS Tower 1920W HV"
                                                                                    (...)

As you can see in the above example, the device name is exposed three times, through three different MIBs:

- Generic MIB-II (RFC 1213):

      .iso.org.dod.internet.mgmt.mib-2.system.sysDescr.0 = STRING: Dell UPS Tower 1920W HV
      .1.3.6.1.2.1.1.1.0 = STRING: Dell UPS Tower 1920W HV

- UPS MIB (RFC 1628):

      .iso.org.dod.internet.mgmt.mib-2.upsMIB.upsObjects.upsIdent.upsIdentModel = STRING: "Dell UPS Tower 1920W HV"
      .1.3.6.1.2.1.33.1.1.2.0 = STRING: "Dell UPS Tower 1920W HV"

- DELL SNMP UPS MIB:

      .iso.org.dod.internet.private.enterprises.674.10902.2.100.1.0 = STRING: "Dell UPS Tower 1920W HV"

But only the two last can serve useful data for NUT.

An highly interesting OID is sysObjectID: its value is an OID that refers to the main MIB of the device. In the above example, the device points us at the Dell UPS MIB. sysObjectID, also called "sysOID" is used by snmp-ups to find the right mapping structure.

For more information on SNMP, refer to the Wikipedia article, or browse the Internet.

To be able to convert values, NUT SNMP subdrivers need to provide:

- manufacturer-specific sysOID, to determine which lookup structure applies to which devices,
- a mapping of SNMP variables to NUT variables,
- a mapping of SNMP values to NUT values.

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. At the time of writing this document, snmp-ups doesn’t provide such mechanisms (only formatting ones), but it is planned in a future release.

#### Creating a subdriver

In order to create a subdriver, you will need the following:

- the "MIB definition file. This file has a ".mib" extension, and is generally available on the accompanying disc, or on the manufacturer website. It should either be placed in a system directory (/usr/share/mibs/ or equivalent), or pointed using -M option,
- a network access to the device
- OR information dumps.

You can create an initial "stub" subdriver for your device by using the helper script scripts/subdriver/gen-snmp-subdriver.sh. Note that this only creates a "stub" which MUST be customized to be useful (see CUSTOMIZATION below).

You have two options to run gen-snmp-subdriver.sh:

##### mode 1: get SNMP data from a real agent

This method requires to have a network access to the device, in order to automatically retrieve the needed information.

You have to specify the following parameters:

- -H host address: is the SNMP host IP address or name
- -c community: is the SNMP v1 community name (default: public)"

For example:

    $ gen-snmp-subdriver.sh -H W.X.Y.Z -c foobar -n <MIB name>.mib

##### mode 2: get data from files

This method does not require direct access to the device, at least not for the one using gen-snmp-subdriver.sh.

The following SNMP data need to be dumped first:

- sysOID value: for example .1.3.6.1.4.1.705.1

- a numeric SNMP walk (OIDs in dotted numeric format) of the tree pointed by sysOID. For example:

      snmpwalk -On -c foobar W.X.Y.Z .1.3.6.1.4.1.705.1 > snmpwalk-On.log

- a textual SNMP walk (OIDs in string format) of the tree pointed by sysOID. For example:

      snmpwalk -Os -c foobar W.X.Y.Z .1.3.6.1.4.1.705.1 > snmpwalk-Os.log

### Note

If the OID are only partially resolved (i.e, there are still parts expressed in numeric form), then try using -M to point your .mib file.

Then call the script using:

    $ gen-snmp-subdriver.sh -s <sysOID value> <numeric SNMP walk> <string SNMP walk>

For example:

    $ gen-snmp-subdriver.sh -s .1.3.6.1.4.1.705.1 snmpwalk-On.log snmpwalk-Os.log

This script prompts you for a name for the subdriver if you don’t provide it with -n. Use only letters and digits, and use natural capitalization such as "Camel" (not "camel" or "CAMEL", apart if it natural). The script may prompt you for additional information.

##### Integrating the subdriver with snmp-ups

Beside of the mandatory customization, there are a few things that you have to do, as mentioned at the end of the script:

- edit drivers/snmp-ups.h and add #include "<HFILE>.h", where <HFILE> is the name of the header file, with the .h extension,
- edit drivers/snmp-ups.c and bump DRIVER_VERSION by adding "0.01".
- also add "&<LDRIVER>" to snmp-ups.c:mib2nut[] list, where <LDRIVER> is the lower case driver name
- add "<LDRIVER>-mib.c" to snmp_ups_SOURCES in drivers/Makefile.am
- add "<LDRIVER>-mib.h" to dist_noinst_HEADERS in drivers/Makefile.am
- copy "<LDRIVER>-mib.c" and "<LDRIVER>-mib.h" to ../drivers/

- finally call the following, from the top level directory, to test compilation:

      $ autoreconf && configure && make

You can already start experimenting with the new subdriver; but all data will be prefixed by "unmapped.". You will now have to customize it.

##### CUSTOMIZATION

The initially generated subdriver code is only a stub (mainly a big C structure to be precise), 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 (commonly wrapped into `snmp_info_default()` macros for portability), 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 source files to see how the currently implemented SNMP subdrivers are written:

- apc-mib.c/h
- baytech-mib.c/h
- bestpower-mib.c/h
- compaq-mib.c/h
- cyberpower-mib.c/h
- eaton-*-mib.c/h
- ietf-mib.c/h
- mge-mib.c/h
- netvision-mib.c/h
- powerware-mib.c/h
- raritan-pdu-mib.c
- huawei-mib.c/h

To help you, above each entry in <LDRIVER>-mib.c, there is a comment that displays the textual OID name. For example, the following entry:

    /* upsMIB.upsObjects.upsIdent.upsIdentModel = STRING: "Dell UPS Tower 1920W HV" */
    snmp_info_default("ups.model", ST_FLAG_STRING, SU_INFOSIZE,
        ".1.3.6.1.4.1.2254.2.4.1.1.0", NULL, SU_FLAG_OK, NULL),

Many times, only the first field will need to be modified, to map to an actual NUT variable name.

Check the NUT command and variable naming scheme section first to find a name that matches the OID name (closest fit). If nothing matches, contact the upsdev list, and we’ll figure it out.

In the above example, the right NUT variable is obviously "device.model".

The MIB definition file (.mib) also contains some description of these OIDs, along with the possible enumerated values.

### Note

To test existing data points (including those not yet translated to standard NUT mappings conforming to NUT command and variable naming scheme), you can use custom drivers built after you `./configure --with-unmapped-data-points`. Production driver builds must not include any non-standard names.

### 5.25. How to make a new subdriver to support another Q* UPS

#### Overall concept

The NUT "nutdrv_qx" driver is a meta-driver that handles Q* UPS devices.

It consists of a core driver that handles most of the work of talking to the hardware, and several sub-drivers to handle specific UPS manufacturers.

Adding support for a new UPS device is easy, because it requires only the creation of a new sub-driver.

### Note

Due to historic reasons, there is a bit of a mess with terminology here: among the set of driver parameters passed on command-line or via `ups.conf`, the `subdriver` value is for Serial-over-USB dialect ("usbsubdriver" in code), and the `protocol` value is for Qx dialect (but referred to as "subdriver" in most of the documentation, and variable names in the code itself)..

An additional set of source code files named `nutdrv_qx_subdrivername.{c,h}` defines a `subdriver_t` entry that is listed as in `subdrivers_list` array in the main `nutdrv_qx.c` file. However, in `ups.conf` this entity is referred to via the communication `protocol` keyword, if the end-user wants to pick one explicitly (bypassing auto-detection).

The string value of each `protocol` setting is derived from the respective source module’s self-identification, by picking the first space-separated token (its `NAME`, assuming a `NAME VERSION` pattern) and making a case-insensitive comparison. The self-identification value is maintained as a string macro at the top of each module’s C source file, and is passed to the main driver code as part of `subdriver_t` structure instance defined in the end of the file.

Confusingly, there is also an optional USB `subdriver` setting (available when the driver is built with USB support), for "Serial-over-USB subdriver selection", corresponding to entries in the `usbsubdriver` array and several `usbsubdrvname_command()` methods defined directly in `nutdrv_qx.c`.

There are also methods called `usbsubdrvname_subdriver()` which are called via `qx_usb_id[]` array for USB VendorID/ProductID/iManufacturer/iProduct based matching, and typically set the `subdriver_command` variable to point to the corresponding `usbsubdrvname_command()` method when auto-detection happens. Otherwise, this variable is set according to a text name requested in the `subdriver` driver parameter.

#### Creating a subdriver

In order to develop a new subdriver for a specific UPS you have to know the "idiom" (dialect of the protocol) spoken by that device.

This kind of devices speaks idioms that can be summed up as follows:

- We send the UPS a query for one or more information

  - If the query is supported by the device, we’ll get a reply that is mostly of a fixed length, therefore, in most cases, each information starts and ends always at the same indexes

- We send the UPS a command

  - If the command is supported by the device, the UPS will either take action without any reply or reply us with a device-specific answer signaling that the command has been accepted (e.g. `ACK`)
- If the query/command isn’t supported by the device we’ll get either the query/command echoed back or a device-specific reply signaling that it has been rejected (e.g. `NAK`)

To be supported by this driver the idiom spoken by the UPS must comply to these conditions.

#### Writing a subdriver

You have to fill the `subdriver_t` structure:

    typedef struct {
            const char      *name;
            int             (*claim)(void);
            item_t          *qx2nut;
            void            (*initups)(void);
            void            (*initinfo)(void);
            void            (*makevartable)(void);
            const char      *accepted;
            const char      *rejected;
    #ifdef TESTING
            testing_t       *testing;
    #endif  /* TESTING */
    } subdriver_t;

Where:

`name`
  Name of this subdriver: name of the `protocol` that will need to be set in the `ups.conf` file to use this subdriver plus the internal version of it separated by a space (e.g. "`Megatec 0.01`").
`claim`
  This function allows the subdriver to "claim" a device: return `1` if the device is supported by this subdriver, else `0`.
`qx2nut`
  Main table of vars and instcmds: an array of `item_t` mapping a UPS idiom to NUT.
`initups` (optional)
  Subdriver-specific `upsdrv_initups`. This function will be called at the end of nutdrv_qx’s own `upsdrv_initups`.
`initinfo` (optional)
  Subdriver-specific `upsdrv_initinfo`. This function will be called at the end of nutdrv_qx’s own `upsdrv_initinfo`.
`makevartable` (optional)
  Function to add subdriver-specific `ups.conf` vars and flags. Make sure not to collide with other subdrivers' vars and flags.
`accepted` (optional)
  String to match if the driver is expecting a reply from the UPS on instcmd/setvar in case of success. This comparison is done after the answer we got back from the UPS has been processed to get the value we are searching, so you don’t have to include the trailing carriage return (`\r`) and you can decide at which index of the answer the value should start or end setting the appropriate `from` and `to` in the `item_t` (see Mapping an idiom to NUT).
`rejected` (optional)
  String to match if the driver is expecting a reply from the UPS in case of error. Note that this comparison is done on the answer we got back from the UPS before it has been processed, so include also the trailing carriage return (`\r`) and whatever character is expected.
`testing`

  Testing table (an array of `testing_t`) that will hold the commands and the replies used for testing the subdriver.

  `testing_t`:

      typedef struct {
              const char      *cmd;
              const char      answer[SMALLBUF];
              const int       answer_len;
      } testing_t;

  Where:

  `cmd`
    Command to match.
  `answer`

    Answer for that command.

    ### Note

    If `answer` contains inner `\0`s, in order to preserve them, `answer_len` as well as an `item_t`'s `preprocess_answer()` function must be set.

  `answer_len`

    Answer length:

    - if set to `-1` → auto calculate answer length (treat `answer` as a null-terminated string),
    - otherwise → use the provided length (if reasonable) and preserve inner `\0`s (treat `answer` as a sequence of bytes till the `item_t`'s `preprocess_answer()` function gets called).

  For more information, see Mapping an idiom to NUT.

#### Mapping an idiom to NUT

If you understand the idiom spoken by your device, you can easily map it to NUT variables and instant commands, filling `qx2nut` with an array of `item_t` data structure:

    typedef struct item_t {
            const char      *info_type;
            const int       info_flags;
            info_rw_t       *info_rw;
            const char      *command;
            char            answer[SMALLBUF];
            const int       answer_len;
            const char      leading;
            char            value[SMALLBUF];
            const int       from;
            const int       to;
            const char      *dfl;
            unsigned long   qxflags;
            int             (*preprocess_command)(struct item_t *item, char *command, const size_t commandlen);
            int             (*preprocess_answer)(struct item_t *item, const int len);
            int             (*preprocess)(struct item_t *item, char *value, const size_t valuelen);
    } item_t;

Where:

`info_type`
  NUT variable name, otherwise, if `QX_FLAG_NONUT` is set, name to print to logs and if both `QX_FLAG_NONUT` and `QX_FLAG_SETVAR` are set, name of the var to retrieve from `ups.conf`.
`info_flags`
  NUT flags (`ST_FLAG_*` values to set in `dstate_addinfo`).
`info_rw`

  An array of `info_rw_t` to handle r/w variables:

  - If `ST_FLAG_STRING` is set in `info_flags` it’ll be used to set the length of the string (in `dstate_setaux`)
  - If `QX_FLAG_ENUM` is set in `qxflags` it’ll be used to set enumerated values (in `dstate_addenum`)
  - If `QX_FLAG_RANGE` is set in `qxflags` it’ll be used to set range boundaries (in `dstate_addrange`)

  ### Note

  If `QX_FLAG_SETVAR` is set the value given by the user will be checked against these infos.

  `info_rw_t`:

      typedef struct {
              char    value[SMALLBUF];
              int     (*preprocess)(char *value, const size_t len);
      } info_rw_t;

  Where:

  `value`
    Value for enum/range, or length for `ST_FLAG_STRING`.
  `preprocess(value, len)`

    Optional function to preprocess range/enum `value`.

    This function will be given `value` and its `size_t` and must return either `0` if `value` is supported or `-1` if not supported.

`command`
  Command sent to the UPS to get answer, or to execute an instant command, or to set a variable.
`answer`

  Answer from the UPS, filled at runtime.

  ### Note

  If you expect a non-valid C string (e.g.: inner `\0`s) or need to perform actions before the answer is used (and treated as a null-terminated string), you should set a `preprocess_answer()` function.

`answer_len`
  Expected minimum length of the answer. Set it to `0` if there’s no minimum length to look after.
`leading`
  Expected leading character of the answer (optional), e.g. `#`, `(` …
`value`
  Value from the answer, filled at runtime (i.e. `answer` in the interval [`from` to `to`]).
`from`
  Position of the starting character of the info we’re after in the answer.
`to`
  Position of the ending character of the info we’re after in the answer: use `0` if all the remaining of the line is needed.
`dfl`

  printf format to store value from the UPS in NUT variables. Set it either to `%s` for strings or to a floating point specifier (e.g. `%.1f`) for numbers.

  Otherwise:

  - If `QX_FLAG_ABSENT` → default value
  - If `QX_FLAG_CMD` → default command value
`qxflags`

  Driver’s own flags.

  `QX_FLAG_STATIC` | Retrieve this variable only once.
  `QX_FLAG_SEMI_STATIC` | Retrieve this info smartly, i.e. only when a command/setvar is executed and we expect that data could have been changed.
  `QX_FLAG_ABSENT` | Data is absent in the device, use default value.
  `QX_FLAG_QUICK_POLL` | Mandatory vars.
  `QX_FLAG_CMD` | Instant command.
  `QX_FLAG_SETVAR` | The var is settable and the actual item stores info on how to set it.
  `QX_FLAG_TRIM` | This var’s value need to be trimmed of leading/trailing spaces/hashes.
  `QX_FLAG_ENUM` | Enum values exist.
  `QX_FLAG_RANGE` | Ranges for this var are available.
  `QX_FLAG_NONUT` | This var doesn’t have a corresponding var in NUT.
  `QX_FLAG_SKIP` | Skip this var: this item won’t be processed.

  ### Note

  The driver will run a so-called `QX_WALKMODE_INIT` in `initinfo` walking through all the items in `qx2nut`, adding instant commands and the like. From then on it’ll run a so-called `QX_WALKMODE_QUICK_UPDATE` just to see if the UPS is still there and then it’ll do a so-called `QX_WALKMODE_FULL_UPDATE` to update all the vars.

  If there’s a problem with a var in `QX_WALKMODE_INIT`, the driver will automagically set `QX_FLAG_SKIP` on it and then it’ll skip that item in `QX_WALKMODE_QUICK_UPDATE`/`QX_WALKMODE_FULL_UPDATE`, provided that the item has not the flag `QX_FLAG_QUICK_POLL` set, in that case the driver will set `datastale`.

`preprocess_command(item, command, commandlen)`
  Last chance to preprocess the command to be sent to the UPS (e.g. to add CRC, …). This function is given the currently processed item (`item`), the command to be sent to the UPS (`command`) and its size_t (`commandlen`). Return `-1` in case of errors, else `0`. `command` must be filled with the actual command to be sent to the UPS.
`preprocess_answer(item, len)`
  Function to preprocess the answer we got from the UPS before we do anything else (e.g. for CRC, decoding, …). This function is given the currently processed item (`item`) with the answer we got from the UPS unmolested and already stored in `item`'s `answer` and the length of that answer (`len`). Return `-1` in case of errors, else the length of the newly allocated `item`'s `answer` (from now on, treated as a null-terminated string).
`preprocess(item, value, valuelen)`

  Function to preprocess the data from/to the UPS: you are given the currently processed item (`item`), a char array (`value`) and its `size_t` (`valuelen`). Return `-1` in case of errors, else `0`.

  - If `QX_FLAG_SETVAR`/`QX_FLAG_CMD` is set then the item is processed before the command is sent to the UPS so that you can fill it with the value provided by the user.

    ### Note

    In this case `value` must be filled with the command to be sent to the UPS.

  - Otherwise the function will be used to process the value we got from the answer of the UPS before it’ll get stored in a NUT variable.

    ### Note

    In this case `value` must be filled with the processed value already compliant to NUT standards.

### Important

You must provide an `item_t` with `QX_FLAG_SETVAR` and its boundaries set for both `ups.delay.start` and `ups.delay.shutdown` to map the driver variables `ondelay` and `offdelay`, as they will be used in the shutdown sequence.

### Tip

In order to keep the data flow at minimum you should keep together the items in `qx2nut` that need data from the same query (i.e. `command`): doing so the driver will send the query only once and then every `item_t` processed after the one that got the answer, provided that it’s filled with the same `command` and that the answer wasn’t `NULL`, will get that `answer`.

#### Examples

The following examples are from the `voltronic` subdriver.

##### Simple vars

We know that when the UPS is queried for status with `QGS\r`, it replies with something like `(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r` and we want to access the output voltage (the third token, in this case `229.8`).

    > [QGS\r]
    < [(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r]
       0123456789012345678901234567890123456789012345678901234567890123456789012345
       0         1         2         3         4         5         6         7

Here’s the `item_t`:

    { "output.voltage", 0, NULL, "QGS\r", "", 76, '(', "", 12, 16, "%.1f", 0, NULL, NULL, NULL },

`info_type` | `output.voltage`
`info_flags` | `0`
`info_rw` | `NULL`
`command` | `QGS\r`
`answer` | Filled at runtime
`answer_len` | `76`
`leading` | `(`
`value` | Filled at runtime
`from` | `12` → the index at which the info (i.e. `value`) starts
`to` | `16` → the index at which the info (i.e. `value`) ends
`dfl` | `%.1f` We are expecting a number, so at first the core driver will check if it’s made up entirely of digits/points/spaces, then it’ll convert it into a double. Because of that we need to provide a floating point specifier.
`qxflags` | `0`
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `NULL`

##### Mandatory vars

Also from `QGS\r`, we want to process the 9th status bit `10000000` `0` `001` that tells us whether the UPS is shutting down or not.

    > [QGS\r]
    < [(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r]
       0123456789012345678901234567890123456789012345678901234567890123456789012345
       0         1         2         3         4         5         6         7

Here’s the `item_t`:

    { "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 71, 71, "%s", QX_FLAG_QUICK_POLL, NULL, NULL, voltronic_status },

`info_type` | `ups.status`
`info_flags` | `0`
`info_rw` | `NULL`
`command` | `QGS\r`
`answer` | Filled at runtime
`answer_len` | `76`
`leading` | `(`
`value` | Filled at runtime
`from` | `71` → the index at which the info (i.e. `value`) starts
`to` | `71` → the index at which the info (i.e. `value`) ends
`dfl` | `%s` Since a `preprocess` function is defined for this item, this could have been `NULL`, however, if we want — like here — we can use it in our `preprocess` function.
`qxflags` | `QX_FLAG_QUICK_POLL` → this item will be polled every time the driver will check for updates. Since this item is mandatory to run the driver, if a problem arises in `QX_WALKMODE_INIT` the driver won’t skip it and it will set `datastale`.
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_status` This function will be called after the `command` has been sent to the UPS and we got back the `answer` and stored the `value` in order to process it to NUT standards: in this case we will convert the binary `value` to a NUT status.

##### Settable vars

So your UPS reports its battery type when queried for `QBT\r`; we are expecting an answer like `(01\r` and we know that the values can be mapped as follows: `00` → "Li", `01` → "Flooded" and `02` → "AGM".

    > [QBT\r]
    < [(01\r]       <- 00="Li", 01="Flooded" or 02="AGM"
       0123
       0

Here’s the `item_t`:

    { "battery.type", ST_FLAG_RW, voltronic_e_batt_type, "QBT\r", "", 4, '(', "", 1, 2, "%s",
      QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, NULL, voltronic_p31b },

`info_type` | `battery.type`
`info_flags` | `ST_FLAG_RW` → this is a r/w var
`info_rw` | `voltronic_e_batt_type` The values stored here will be added to the NUT variable, setting its boundaries: in this case `Li`, `Flooded` and `AGM` will be added as enumerated values.
`command` | `QBT\r`
`answer` | Filled at runtime
`answer_len` | `4`
`leading` | `(`
`value` | Filled at runtime
`from` | `1` → the index at which the info (i.e. `value`) starts
`to` | `2` → the index at which the info (i.e. `value`) ends
`dfl` | `%s` Since a `preprocess` function is defined for this item, this could have been `NULL`, however, if we want — like here — we can use it in our `preprocess` function.
`qxflags` | `QX_FLAG_SEMI_STATIC` → this item changes — and will therefore be updated — only when we send a command/setvar to the UPS `QX_FLAG_ENUM` → this r/w variable is of the enumerated type and the enumerated values are listed in the `info_rw` structure (i.e. `voltronic_e_batt_type`)
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_p31b` This function will be called after the `command` has been sent to the UPS and we got back the `answer` and stored the `value` in order to process it to NUT standards: in this case we will check if the value is in the range and then publish the human readable form of it (i.e. `Li`, `Flooded` or `AGM`).

We also know that we can change battery type with the `PBTnn\r` command; we are expecting either `(ACK\r` if the command succeeded or `(NAK\r` if the command is rejected.

    > [PBTnn\r]             nn = 00/01/02
    < [(ACK\r]
       01234
       0

Here’s the `item_t`:

    { "battery.type", 0, voltronic_e_batt_type, "PBT%02.0f\r", "", 5, '(', "", 1, 4, NULL,
      QX_FLAG_SETVAR | QX_FLAG_ENUM, NULL, NULL, voltronic_p31b_set },

`info_type` | `battery.type`
`info_flags` | `0`
`info_rw` | `voltronic_e_batt_type` The value provided by the user will be automagically checked by the core nutdrv_qx driver against the enumerated values already set by the non setvar item (i.e. `Li`, `Flooded` or `AGM`), so this could have been `NULL`, however if we want — like here — we can use it in our `preprocess` function.
`command` | `PBT%02.0f\r`
`answer` | Filled at runtime
`answer_len` | `5` ← either `(NAK\r` or `(ACK\r`
`leading` | `(`
`value` | Filled at runtime
`from` | `1` → the index at which the info (i.e. `value`) starts
`to` | `3` → the index at which the info (i.e. `value`) ends
`dfl` | Not used for `QX_FLAG_SETVAR`
`qxflags` | `QX_FLAG_SETVAR` → this item is used to set the variable `info_type` (i.e. `battery.type`) `QX_FLAG_ENUM` → this r/w variable is of the enumerated type and the enumerated values are listed in the `info_rw` structure (i.e. `voltronic_e_batt_type`)
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_p31b_set` This function will be called before the `command` is sent to the UPS so that we can fill `command` with the value provided by the user: in this case the function will simply translate the human readable form of battery type (i.e. `Li`, `Flooded` or `AGM`) to the UPS compliant type (i.e. `00`, `01` and `02`) and then fill `value` (the second argument passed to the `preprocess` function).

##### Instant commands

We know that we have to send to the UPS `Tnn\r` or `T.n\r` in order to start a battery test lasting `nn` minutes or `.n` minutes: we are expecting either `(ACK\r` on success or `(NAK\r` if the command is rejected.

    > [Tnn\r]
    < [(ACK\r]
       01234
       0

Here’s the `item_t`:

    { "test.battery.start", 0, NULL, "T%s\r", "", 5, '(', "", 1, 4, NULL, QX_FLAG_CMD, NULL, NULL, voltronic_process_command },

`info_type` | `test.battery.start`
`info_flags` | `0`
`info_rw` | `NULL`
`command` | `T%s\r`
`answer` | Filled at runtime
`answer_len` | `5` ← either `(NAK\r` or `(ACK\r`
`leading` | `(`
`value` | Filled at runtime
`from` | `1` → the index at which the info (i.e. `value`) starts
`to` | `3` → the index at which the info (i.e. `value`) ends
`dfl` | Not used for `QX_FLAG_CMD`
`qxflags` | `QX_FLAG_CMD` → this item is an instant command that will be fired when `info_type` (i.e. `test.battery.start`) is called
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_process_command` This function will be called before the `command` is sent to the UPS so that we can fill `command` with the value provided by the user: in this case the function will check if the value is in the accepted range and then fill `value` (the second argument passed to the `preprocess` function) with `command` and the given value.

##### Information absent in the device

In order to set the server-side var `ups.delay.start`, that will be then used by the driver, we have to provide the following `item_t`:

    { "ups.delay.start", ST_FLAG_RW, voltronic_r_ondelay, NULL, "", 0, 0, "", 0, 0, "180",
      QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, voltronic_process_setvar },

`info_type` | `ups.delay.start`
`info_flags` | `ST_FLAG_RW` → this is a r/w var
`info_rw` | `voltronic_r_ondelay` The values stored here will be added to the NUT variable, setting its boundaries: in this case `0` and `599940` will be set as the minimum and maximum value of the variable’s range. Those values will then be used by the driver to check the user provided value.
`command` | Not used for `QX_FLAG_ABSENT`
`answer` | Not used for `QX_FLAG_ABSENT`
`answer_len` | Not used for `QX_FLAG_ABSENT`
`leading` | Not used for `QX_FLAG_ABSENT`
`value` | Not used for `QX_FLAG_ABSENT`
`from` | Not used for `QX_FLAG_ABSENT`
`to` | Not used for `QX_FLAG_ABSENT`
`dfl` | `180` ← the default value that will be set for this variable
`qxflags` | `QX_FLAG_ABSENT` → this item isn’t available in the device `QX_FLAG_SETVAR` → this item is used to set the variable `info_type` (i.e. `ups.delay.start`) `QX_FLAG_RANGE` → this r/w variable has a settable range and its boundaries are listed in the `info_rw` structure (i.e. `voltronic_r_ondelay`)
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_process_setvar` This function will be called, in setvar, before the driver stores the value in the NUT var: here it’s used to truncate the user-provided value to the nearest settable interval.

##### Information not yet available in NUT

If your UPS reports some data items that are not yet available as NUT variables and you need to process them, you can add them in `item_t` data structure adding the `QX_FLAG_NONUT` flag to its `qxflags`: the info will then be printed to the logs.

So we know that the UPS reports actual input/output phase angles when queried for `QPD\r`:

    > [QPD\r]
    < [(000 120\r]  <- Input Phase Angle -- Output Phase Angle
       012345678
       0

Here’s the `item_t` for input phase angle:

    { "input_phase_angle", 0, NULL, "QPD\r", "", 9, '(', "", 1, 3, "%03.0f",
      QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, voltronic_phase },

`info_type` | `input_phase_angle` This information will be used to print the value we got back from the UPS in the logs.
`info_flags` | `0`
`info_rw` | `NULL`
`command` | `QPD\r`
`answer` | Filled at runtime
`answer_len` | `9`
`leading` | `(`
`value` | Filled at runtime
`from` | `1` → the index at which the info (i.e. `value`) starts
`to` | `3` → the index at which the info (i.e. `value`) ends
`dfl` | `%03.0f` If there’s no `preprocess` function, the format is used to print the value to the logs. Here instead it’s used by the `preprocess` function.
`qxflags` | `QX_FLAG_STATIC` → this item doesn’t change `QX_FLAG_NONUT` → this item doesn’t have yet a NUT variable
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_phase` This function will be called after the `command` has been sent to the UPS so that we can parse the value we got back and check it.

Here’s the `item_t` for output phase angle:

    { "output_phase_angle", ST_FLAG_RW, voltronic_e_phase, "QPD\r", "", 9, '(', "", 5, 7, "%03.0f",
      QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_NONUT, NULL, NULL, voltronic_phase },

`info_type` | `output_phase_angle` This information will be used to print the value we got back from the UPS in the logs.
`info_flags` | `ST_FLAG_RW` This could also be `0` (it’s not really used by the driver), but it’s set to `ST_FLAG_RW` for cohesion with other rw vars — also, if ever a NUT variable would become available for this item, it’ll be easier to change this item and its `QX_FLAG_SETVAR` counterpart to use it.
`info_rw` | `voltronic_e_phase` Enumerated list of available value (here: `000`, `120`, `240` and `360`). Since `QX_FLAG_NONUT` is set the driver will print those values to the logs, plus you could use it in the `preprocess` function to check the value we got back from the UPS (as done here).
`command` | `QPD\r`
`answer` | Filled at runtime
`answer_len` | `9`
`leading` | `(`
`value` | Filled at runtime
`from` | `5` → the index at which the info (i.e. `value`) starts
`to` | `7` → the index at which the info (i.e. `value`) ends
`dfl` | `%03.0f` If there’s no `preprocess` function, the format is used to print the value to the logs. Here instead it’s used by the `preprocess` function.
`qxflags` | `QX_FLAG_SEMI_STATIC` → this item changes — and will therefore be updated — only when we send a command/setvar to the UPS `QX_FLAG_ENUM` → this r/w variable is of the enumerated type and the enumerated values are listed in the `info_rw` structure (i.e. `voltronic_e_phase`). `QX_FLAG_NONUT` → this item doesn’t have yet a NUT variable
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_phase` This function will be called after the `command` has been sent to the UPS so that we can parse the value we got back and check it. Here it’s used also to store a var that will then be used to check the value in setvar’s preprocess function.

If you need also to change some values in the UPS you can add a `ups.conf` var/flag in the subdriver’s own `makevartable` and then process it adding to its `qxflags` both `QX_FLAG_NONUT` and `QX_FLAG_SETVAR`: this item will be processed only once in `QX_WALKMODE_INIT`.

The driver will check if the var/flag is defined in `ups.conf`: if so, it’ll then call `setvar` passing to this item the defined value, if any, and then it’ll print the results in the logs.

We know we can set output phase angle sending `PPDnnn\r` to the UPS:

    > [PPDn\r]              n = (000, 120, 180 or 240)
    < [(ACK\r]
       01234
       0

Here’s the `item_t`

    { "output_phase_angle", 0, voltronic_e_phase, "PPD%03.0f\r", "", 5, '(', "", 1, 4, NULL,
      QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT, NULL, NULL, voltronic_phase_set },

`info_type` | `output_phase_angle` This information will be used to print the value we got back from the UPS in the logs and to retrieve the user-provided value in `ups.conf`. So, name it after the variable you created to use in `ups.conf` in the subdriver’s own `makevartable`.
`info_flags` | `0`
`info_rw` | `voltronic_e_phase` Enumerated list of available values (here: `000`, `120`, `240` and `360`). The value provided by the user will be automagically checked by the core nutdrv_qx driver against the enumerated values stored here.
`command` | `PPD%03.0f\r`
`answer` | Filled at runtime
`answer_len` | `5` ← either `(NAK\r` or `(ACK\r`
`leading` | `(`
`value` | Filled at runtime
`from` | `1` → the index at which the info (i.e. `value`) starts
`to` | `3` → the index at which the info (i.e. `value`) ends
`dfl` | Not used for `QX_FLAG_SETVAR`
`qxflags` | `QX_FLAG_SETVAR` → this item is used to set the variable `info_type` (i.e. `output_phase_angle`) `QX_FLAG_ENUM` → this r/w variable is of the enumerated type and the enumerated values are listed in the `info_rw` structure (i.e. `voltronic_e_phase`). `QX_FLAG_NONUT` → this item doesn’t have yet a NUT variable
`preprocess_command` | `NULL`
`preprocess_answer` | `NULL`
`preprocess` | `voltronic_phase_set` This function will be called before the `command` is sent to the UPS so that we can check user-provided value and fill `command` with it and then fill `value` (the second argument passed to the `preprocess` function).

#### Support functions

You are already given the following functions:

`int instcmd(const char *cmdname, const char *extradata)`

  Execute an instant command. In detail:

  - look up the given `cmdname` in the qx2nut data structure (if not found, try to fallback to commonly known commands);
  - if `cmdname` is found, call its preprocess function, passing to it `extradata`, if any, otherwise its `dfl` value, if any;
  - send the command to the device and check the reply.

  Return `STAT_INSTCMD_INVALID` if the command is invalid, `STAT_INSTCMD_FAILED` if it failed, `STAT_INSTCMD_HANDLED` on success.

`int setvar(const char *varname, const char *val)`
  Set r/w variable to a value after it has been checked against its `info_rw` structure. Return `STAT_SET_HANDLED` on success, otherwise `STAT_SET_UNKNOWN`.
`item_t *find_nut_info(const char *varname, const unsigned long flag, const unsigned long noflag)`

  Find an item of `item_t` type in `qx2nut` data structure by its `info_type`, optionally filtered by its `qxflags`, and return it if found, otherwise return `NULL`.

  - `flag`: flags that have to be set in the item, i.e. if one of the flags is absent in the item it won’t be returned.
  - `noflag`: flags that have to be absent in the item, i.e. if at least one of the flags is set in the item it won’t be returned.
`int qx_process(item_t *item, const char *command)`
  Send `command` (a null-terminated byte string) or, if it is `NULL`, send the command stored in the `item` to the UPS and process the reply, saving it in `item`'s `answer`. Return `-1` on errors, `0` on success.
`int ups_infoval_set(item_t *item)`
  Process the value we got back from the UPS (set status bits and set the value of other parameters), calling the `item`-specific `preprocess` function, if any, otherwise executing the standard preprocessing (including trimming if `QX_FLAG_TRIM` is set). Return `-1` on failure, `0` for a status update and `1` in all other cases.
`int qx_status(void)`
  Return the currently processed status so that it can be checked with one of the `status_bit_t` passed to the `STATUS()` macro (see `nutdrv_qx.h`).
`void update_status(const char *nutvalue)`
  If you need to edit the current status call this function with one of the NUT status (all but `OB` are supported, simply set it as not `OL`); prefix them with an exclamation mark if you want to clear them from the status (e.g. `!OL`).

#### Armac Subdriver

Armac subdriver is based on reverse engineering of Power Manager II software by Richcomm Technologies written in 2005 that is still (as of 2023) being distributed as a valid software for freshly sold UPS of various manufacturers. It uses commands as defined for Megatec protocol - but has a different communication mechanism.

It uses two types of USB interrupt transfers: - 4 bytes to send a command (usually single transfer). - 6 byte chunk to read a reply (multiple transfers).

Transfers are similar to those of the richcomm nut driver, but the transferred data is not short binary commands. Instead, serial text data is overlaid in these transfers in a way that creates a badly made USB serial interface. UPS reply looks similar to this:

     0  1  2  3  4  5
    HL 00 00 00 00 00

HL is a control byte. Its high nibble meaning is unknown. It changes between two possible values during transmission. Low nibble encodes number of bytes that have a meaning in the transaction. For example there are 5 bytes that might contain ASCII serial data, but only some might be valid, and other might be random, stale buffer data, etc.

What follows is set of observed transmissions by various UPSes gathered from Github issues.

##### Transfer dumps

## Vultech V2000

    419.987514     [D4] armac command Q1
    419.988307     [D4] armac cleanup ret i=0 ret=6 ctrl=c0
    420.119402     [D4] read: ret 6 buf 81: 28 30 31 30 30  >(0100<
    420.130383     [D4] read: ret 6 buf c1: 32 30 31 30 30  >20100<
    420.141408     [D4] read: ret 6 buf 82: 33 33 31 30 30  >33100<
    420.152201     [D4] read: ret 6 buf c3: 2e 30 20 30 30  >.0 00<
    420.153237     [D4] read: ret 6 buf 82: 30 30 20 30 30  >00 00<
    420.164299     [D4] read: ret 6 buf c1: 30 30 20 30 30  >00 00<
    420.175293     [D4] read: ret 6 buf 82: 2e 30 20 30 30  >.0 00<
    420.186358     [D4] read: ret 6 buf c3: 20 32 33 30 30  > 2300<
    420.190322     [D4] read: ret 6 buf 83: 33 2e 30 30 30  >3.000<
    420.194323     [D4] read: ret 6 buf c1: 20 2e 30 30 30  > .000<
    420.205358     [D4] read: ret 6 buf 81: 30 2e 30 30 30  >0.000<
    420.216318     [D4] read: ret 6 buf c2: 31 34 30 30 30  >14000<
    420.227445     [D4] read: ret 6 buf 83: 20 34 39 30 30  > 4900<
    420.228334     [D4] read: ret 6 buf c2: 2e 30 39 30 30  >.0900<
    420.239461     [D4] read: ret 6 buf 81: 20 30 39 30 30  > 0900<
    420.250411     [D4] read: ret 6 buf c2: 32 37 39 30 30  >27900<
    420.261405     [D4] read: ret 6 buf 83: 2e 30 20 30 30  >.0 00<
    420.265468     [D4] read: ret 6 buf c3: 32 30 2e 30 30  >20.00<
    420.269465     [D4] read: ret 6 buf 81: 38 30 2e 30 30  >80.00<
    420.280322     [D4] read: ret 6 buf c1: 20 30 2e 30 30  > 0.00<
    420.291469     [D4] read: ret 6 buf 82: 30 30 2e 30 30  >00.00<
    420.302465     [D4] read: ret 6 buf c3: 30 30 31 30 30  >00100<
    420.303511     [D4] read: ret 6 buf 82: 00 30 31 30 30  >          <- This has 0x00 and '0', will be read as "00"
    420.303515     [D3] found null byte in status bits at 43 byte, assuming 0.
    420.314425     [D4] read: ret 6 buf c1: 31 30 31 30 30  >10100<    <- this has '1'
    420.325432     [D4] read: ret 6 buf 81: 0d 30 31 30 30  >.0100<    <- and this finishes with `\r`.
    420.325442     [D3] armac command Q1 response read: '(233.0 000.0 233.0 014 49.0 27.0 20.8 00001001'

    1.185164     [D4] armac command ID
    1.316257     [D4] read: ret 6 buf c1: 23 31 00 30 30  >#1
    1.327309     [D4] read: ret 6 buf 81: 20 31 00 30 30  > 1
    1.338264     [D4] read: ret 6 buf c2: 20 20 00 30 30  >
    1.349151     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
    1.360277     [D4] read: ret 6 buf c2: 20 20 20 30 30  >   00<
    1.371322     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
    1.382265     [D4] read: ret 6 buf c3: 20 20 20 30 30  >   00<
    1.393156     [D4] read: ret 6 buf 82: 20 20 20 30 30  >   00<
    1.404324     [D4] read: ret 6 buf c3: 20 20 20 30 30  >   00<
    1.415342     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
    1.426292     [D4] read: ret 6 buf c2: 20 20 20 30 30  >   00<
    1.437203     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
    1.448328     [D4] read: ret 6 buf c3: 56 34 2e 30 30  >V4.00<
    1.459293     [D4] read: ret 6 buf 82: 31 30 2e 30 30  >10.00<
    1.470274     [D4] read: ret 6 buf c3: 20 20 20 30 30  >   00<
    1.481208     [D4] read: ret 6 buf 82: 20 20 20 30 30  >   00<
    1.492261     [D4] read: ret 6 buf c1: 0d 20 20 30 30  >
    1.492270     [D3] armac command ID response read: '#                           V4.10     '

    4.749667     [D4] armac command F
    4.876638     [D4] read: ret 6 buf 81: 23 31 00 30 30  >#1
    4.887614     [D4] read: ret 6 buf c1: 32 31 00 30 30  >21
    4.898644     [D4] read: ret 6 buf 82: 32 30 00 30 30  >20
    4.909595     [D4] read: ret 6 buf c3: 2e 30 20 30 30  >.0 00<
    4.920648     [D4] read: ret 6 buf 82: 30 30 20 30 30  >00 00<
    4.931629     [D4] read: ret 6 buf c3: 35 20 32 30 30  >5 200<
    4.942601     [D4] read: ret 6 buf 83: 34 2e 30 30 30  >4.000<
    4.953666     [D4] read: ret 6 buf c2: 30 20 30 30 30  >0 000<
    4.964535     [D4] read: ret 6 buf 83: 35 30 2e 30 30  >50.00<
    4.975540     [D4] read: ret 6 buf c2: 30 0d 2e 30 30  >0
    4.975546     [D3] armac command F response read: '#220.0 005 24.00 50.0'

## Armac R/2000I/PSW

    112.966856     [D4] armac command Q1
    112.968197     [D4] armac cleanup ret i=0 ret=6 ctrl=c0         <- Cleanups required.
    113.091193     [D4] read: ret 6 buf 81: 28 30 0d 2e 30  >(0     <- Usually 1-3 bytes available in transfer.
    113.103211     [D4] read: ret 6 buf c1: 30 30 0d 2e 30  >00
    113.115180     [D4] read: ret 6 buf 82: 30 30 0d 2e 30  >00
    113.117144     [D4] read: ret 6 buf c3: 2e 30 20 2e 30  >.0 .0<
    113.120150     [D4] read: ret 6 buf 81: 31 30 20 2e 30  >10 .0<
    113.132178     [D4] read: ret 6 buf c1: 34 30 20 2e 30  >40 .0<
    113.144159     [D4] read: ret 6 buf 82: 30 2e 20 2e 30  >0. .0<
    113.146149     [D4] read: ret 6 buf c3: 30 20 32 2e 30  >0 2.0<
    113.149173     [D4] read: ret 6 buf 81: 32 20 32 2e 30  >2 2.0<
    113.161167     [D4] read: ret 6 buf c1: 37 20 32 2e 30  >7 2.0<
    113.173159     [D4] read: ret 6 buf 82: 2e 30 32 2e 30  >.02.0<
    113.175157     [D4] read: ret 6 buf c3: 20 30 30 2e 30  > 00.0<
    113.178158     [D4] read: ret 6 buf 81: 32 30 30 2e 30  >200.0<
    113.190157     [D4] read: ret 6 buf c1: 20 30 30 2e 30  > 00.0<
    113.202161     [D4] read: ret 6 buf 82: 30 30 30 2e 30  >000.0<
    113.204154     [D4] read: ret 6 buf c3: 2e 30 20 2e 30  >.0 .0<
    113.207150     [D4] read: ret 6 buf 81: 34 30 20 2e 30  >40 .0<
    113.219174     [D4] read: ret 6 buf c1: 36 30 20 2e 30  >60 .0<
    113.231165     [D4] read: ret 6 buf 82: 2e 38 20 2e 30  >.8 .0<
    113.233157     [D4] read: ret 6 buf c3: 20 35 36 2e 30  > 56.0<
    113.237149     [D4] read: ret 6 buf 81: 2e 35 36 2e 30  >.56.0<
    113.249168     [D4] read: ret 6 buf c1: 30 35 36 2e 30  >056.0<
    113.261155     [D4] read: ret 6 buf 83: 20 31 30 2e 30  > 10.0<
    113.263151     [D4] read: ret 6 buf c2: 30 30 30 2e 30  >000.0<
    113.266152     [D4] read: ret 6 buf 81: 31 30 30 2e 30  >100.0<
    113.278161     [D4] read: ret 6 buf c1: 30 30 30 2e 30  >000.0<  <- No Null bytes.
    113.290155     [D4] read: ret 6 buf 82: 30 30 30 2e 30  >000.0<
    113.292159     [D4] read: ret 6 buf c1: 0d 30 30 2e 30  >
    113.292169     [D3] armac command Q1 response read: '(000.0 140.0 227.0 002 00.0 46.8 56.0 10001000'

Next query would return 0x80 control byte - 0 available bytes. This used to terminate transmission, but some UPS don’t work like that.

## Armac R/3000I/PF1

    0.083301     [D4] armac command Q1
    0.164847     [D4] read: ret 6 buf a6: 28 32 34 31 2e  >(241.<
    0.184839     [D4] read: ret 6 buf 86: 35 20 30 30 30  >5 000<
    0.205851     [D4] read: ret 6 buf a6: 2e 30 20 32 33  >.0 23<
    0.226849     [D4] read: ret 6 buf 86: 30 2e 33 20 30  >0.3 0<
    0.247859     [D4] read: ret 6 buf a6: 30 30 20 34 39  >00 49<
    0.268862     [D4] read: ret 6 buf 86: 2e 39 20 32 2e  >.9 2.<
    0.289857     [D4] read: ret 6 buf a6: 32 35 20 34 38  >25 48<
    0.309866     [D4] read: ret 6 buf 86: 2e 30 20 30 30  >.0 00<
    0.330863     [D4] read: ret 6 buf a6: 30 30 30 30 30  >00000<
    0.827913     [D4] read: ret 6 buf 83: 31 0d 30 30 30  >1 000<
    0.827927     [D3] armac command Q1 response read: '(241.5 000.0 230.3 000 49.9 2.25 48.0 00000001'
    0.827954     [D4] armac command ID
    1.394985     [D4] read: ret 6 buf a5: 4e 41 4b 0d 30  >NAK  <
    1.395001     [D3] armac command ID response read: 'NAK'

This UPS sends higher nibble set to 6 often, which exceeds available bytes. Maybe means that more are available. Its serial-USB bridge is probably faster. We read 5 bytes in case 6 nibble is sent. End of transmission is marked by `\r`, no 0 nibble is sent.

#### Notes

You must put the generated files into the `drivers/` subdirectory, with the name of your subdriver preceded by `nutdrv_qx_`, and update `nutdrv_qx.c` by adding the appropriate `#include` line and by updating the definition of `subdriver_list`.

Please, make sure to add your driver in that list in a smart way: if your device supports also the basic commands used by the other subdrivers to claim a device, add something that is unique (i.e. not supported by the other subdrivers) to your device in your claim function and then add it on top of the slightly supported ones in that list.

You must also add the subdriver to `NUTDRV_QX_SUBDRIVERS` list variable in the `drivers/Makefile.am` and call "`autoreconf`" and/or "`./configure`" from the top level NUT directory.

You can then recompile `nutdrv_qx`, and start experimenting with the new subdriver.

For more details, have a look at the currently available subdrivers:

- `nutdrv_qx_bestups.`{`c`,`h`}
- `nutdrv_qx_innovart31.`{`c`,`h`}
- `nutdrv_qx_innovart33.`{`c`,`h`}
- `nutdrv_qx_innovatae.`{`c`,`h`}
- `nutdrv_qx_masterguard.`{`c`,`h`}
- `nutdrv_qx_mecer.`{`c`,`h`}
- `nutdrv_qx_megatec.`{`c`,`h`}
- `nutdrv_qx_megatec-old.`{`c`,`h`}
- `nutdrv_qx_mustek.`{`c`,`h`}
- `nutdrv_qx_q1.`{`c`,`h`}
- `nutdrv_qx_q2.`{`c`,`h`}
- `nutdrv_qx_q6.`{`c`,`h`}
- `nutdrv_qx_voltronic.`{`c`,`h`}
- `nutdrv_qx_voltronic-qs.`{`c`,`h`}
- `nutdrv_qx_voltronic-qs-hex.`{`c`,`h`}
- `nutdrv_qx_zinto.`{`c`,`h`}
- `nutdrv_qx_ablerex.`{`c`,`h`}

Prev |  | Next
| Home |