[Nut-upsdev] help writing a usb hid driver for existing ups

Charles Lepple clepple at gmail.com
Tue Jan 22 13:23:03 UTC 2008

On Jan 22, 2008 7:53 AM, Michael Tokarev <mjt+nut at tls.msk.ru> wrote:
> Charles Lepple wrote:
> > It sounds like you may want to talk with Alexey Sidirov to coordinate
> > support for this device.
> Yeah, I tried to reach him - to no avail so far... ;)
> [Update: he replied, telling he has urgent work to do before
> he will be able to work with all this stuff again]


> Here it is:
> # usbhid-ups -DDD -a hid -u root -x explore -x vendorid=0d9f -x productid=0002
> Network UPS Tools: 0.29 USB communication driver - core 0.32 (2.2.1)
> debug level is '3'
> upsdrv_initups...
> Checking device (0D9F/0002) (002/004)
> - VendorID: 0d9f
> - ProductID: 0002
> Report[get]: (2 bytes) => 00 00
> Path: ffa00001.ffa00001, Type: Input, ReportID: 0x00, Offset: 0, Size: 8, Value: 0.000000
> Report[buf]: (2 bytes) => 00 00
> Path: ffa00001.ffa00002, Type: Output, ReportID: 0x00, Offset: 0, Size: 8, Value: 0.000000
> Report[buf]: (2 bytes) => 00 00
> Path: ffa00001.ffa00003, Type: Feature, ReportID: 0x00, Offset: 0, Size: 8, Value: 0.000000

According to this, all of the input and output packets should be 8
bytes. (HID Features can be read or written.)

The kernel lets you read larger buffers (strace indicates that the
program was requesting 512 bytes, and received 64 in several cases.)

>       Endpoint Descriptor:
>         bLength                 7
>         bDescriptorType         5
>         bEndpointAddress     0x81  EP 1 IN
>         bmAttributes            3
>           Transfer Type            Interrupt
>           Synch Type               None
>           Usage Type               Data
>         wMaxPacketSize     0x0008  1x 8 bytes
>         bInterval              10
>       Endpoint Descriptor:
>         bLength                 7
>         bDescriptorType         5
>         bEndpointAddress     0x02  EP 2 OUT
>         bmAttributes            3
>           Transfer Type            Interrupt
>           Synch Type               None
>           Usage Type               Data
>         wMaxPacketSize     0x0008  1x 8 bytes
>         bInterval              10

bInterval is in milliseconds, so that seems to agree with "left {2,
990000}" from select (initial timeout was {3,0}).

> > Since it is the same protocol as the serial powercom driver, you can
> Well, I hope it is.  But looking at the strings read from the
> device by their program (upsmon) more closely, I'm not very
> sure anymore.  I asked their support, maybe I'll have some
> more info on this.

The tripplite and tripplite_usb drivers are slightly different that way.

> > use the same technique as in the megatec and megatec_usb code: link
> > the core powercom code with the serial functions for the regular
> > powercom driver, and with USB HID code in powercom_usb.c (for
> > instance).
> >
> > There are two ways to write the USB interface code. One is to use
> > libusb functions, which are portable to other OSes like FreeBSD,
> > NetBSD, OS X, and potentially Solaris. The other way (not recommended,
> > but may be easier) is to just use the ioctl() calls that you found
> > from strace.
> >
> > The first method is used by megatec_usb and usbhid-ups. The second
> > method is used by energizerups.
> Ok, I'll try to explore those.  The thing is that I don't know a bit
> about the whole USB thing, and don't have intention to learn it at
> full, either... ;)

Well, hopefully you will find it interesting enough to keep writing
the driver, even if you forget all of this USB stuff later :-)

> By the way, there's a kernel-level usbserial kernel-level driver
> for this device, written by another guy in Russia (I think I
> mentioned that before), -- with that driver and powercom.c
> from nut the device works.  So I wonder if it will be simpler
> to write a user-level serial driver and embed it into
> powercom.c module, instead of fidding with hid.  Either
> way, it's still black magic for me ;)

It's up to you. Relying on a kernel module means that anyone who uses
the driver will have to do some custom compilation every time their
distro updates the kernel, which is not the sort of thing you want
when you are monitoring something as important as the computer power.

> >> 654   time(NULL)                        = 1200921228
> >> 654   ioctl(3, HIDIOCGUSAGE, {report_type=1, report_id=0, field_index=0, usage_index=0, usage_code=ffa00001, value=240}) = 0
> >
> > You will have to do a little reverse engineering here to figure out
> > which fields are being passed in, and what they mean. I think that
> > report_type, report_id and usage_code are passed in, and the others
> > are returned by this ioctl() call.
> Note I patched strace(1) utility to print the info here.  Before,
> it just printed `ioctl(3, HIDIOCGUSAGE, 0x123456789)' - ie, an
> address of the structure.  I looked the ioctl in the linux kernel
> headers and added some code into strace to show the argument.

Oh, I didn't realize strace didn't decode those structures by default.
I guess it does sound silly for me to explain the C/G difference, then

> But again, since I don't know anything about USB and how it works
> internally, the structure fields is of no great use to me... ;)
> >> 654   select(4, [3], NULL, NULL, {3, 0}) = 1 (in [3], left {2, 990000})
> >> 654   read(3, "\1\0\240\377\360\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0", 512) = 64
> >
> > This is most likely an interrupt read over whatever the input pipe is.
> Funny thing is that the data is available almost immediately - the
> select() call never sleeps for too much.  It looks like the device
> is constantly feeding something to the host.  UNLIKE the "pure"
> serial protocol.

The tripplite_usb driver polls the device and looks for a return
string that matches the command sent out previously (it depends on the
return string including the command number, and it also means that you
cannot send the same command twice without another command in

So it may be that your USB-to-serial converter has two "threads" - a
main loop which services the USB interrupt reads, and an interrupt
routine that reads from the serial port. It might just return the same
string over and over until the serial port receives a new string.

- Charles Lepple

More information about the Nut-upsdev mailing list