289 lines
12 KiB
Text
289 lines
12 KiB
Text
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.
|
|
|
|
[width="35%"]
|
|
|================================================
|
|
|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 2>&1 | tee /tmp/info
|
|
|
|
You can create an initial "stub" subdriver for your device by using
|
|
script scripts/subdriver/gen-usbhid-subdriver.sh. Note: this only creates
|
|
a "stub" and needs to be further customized to be 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.
|
|
|
|
|
|
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
|
|
|
|
|
|
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.
|
|
|
|
|
|
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`.
|