Charles Lepple
2017-Mar-11 14:15 UTC
[Nut-upsdev] [nut-upsuser] Copeland Engineering Dockmaster
[moved to nut-upsdev while we figure out the protocol]> On Mar 9, 2017, at 11:22 AM, Drew from Zhrodague <drewzhrodague at zhrodague.net> wrote: > > I'm able to cat /dev/usb/hiddev0 and /dev/hidraw0 - I pipe this through hexdump and I get different types of data from each: > > > hiddev: > > 0002420 00a7 ffa0 00ff 0000 00a7 ffa0 00ff 0000 > 0002430 00a7 ffa0 00a3 0000 00a7 ffa0 0001 0000 > 0002440 00a6 ffa0 0080 0000 00a7 ffa0 0011 0000 > 0002450 00a7 ffa0 00bd 0000 00a7 ffa0 0057 0000 > 0002460 00a7 ffa0 00ff 0000 00a7 ffa0 00ff 0000 > 0002470 00a7 ffa0 00a3 0000 00a7 ffa0 0001 0000 > > > hidraw: > > 0000000 1180 59e1 ffff 01a3 1180 59e2 ffff 01a3 > 0000010 1180 5ae3 ffff 01a3 1180 5ae4 ffff 01a3 > 0000020 1180 5ae5 ffff 01a3 1180 5ae6 ffff 01a3 > 0000030 1180 5ae7 ffff 01a3 1180 5ae8 ffff 01a3 > 0000040 1180 5ae9 ffff 01a3 1180 5aea ffff 01a3 > 0000050 1180 5aeb ffff 01a3 1180 5aec ffff 01a3I was trying to reconcile this with your earlier information from usbhid-dump, and it looks like hexdump is swapping bytes: $ printf 12345678|hexdump 0000000 3231 3433 3635 3837 0000008 You can do one of the following to get the natural order, plus an ASCII dump: $ printf 12345678|hexdump -C 00000000 31 32 33 34 35 36 37 38 |12345678| 00000008 $ printf 12345678|hd 00000000 31 32 33 34 35 36 37 38 |12345678| 00000008> I haven't been able to figure out what the differences between hiddev and hidraw are, but the first one has the state (0001), and the second one has the timers (59e2). It also looks like there are two sentences per line. > > I have put together a bash script to fetch a few lines from the hiddev, and look for a key/value - sometimes what I'm using as a key changes between boots. It may not be a key/value pair.Here's the kernel documentation: https://www.kernel.org/doc/Documentation/hid/hiddev.txt https://www.kernel.org/doc/Documentation/hid/hidraw.txt In both cases, "cat" is triggering the "read()" operation in each driver.> In its basic mode, the hiddev will make these individual > usage changes available to the reader using a struct hiddev_event: > > struct hiddev_event { > unsigned hid; > signed int value; > };I think the "hid" value in the hiddev output is one of "ffa000a6" or "ffa000a7", but byte-swapped (still i686, right?) and hexdump-swapped to "00a6 ffa0" and "00a7 ffa0". The next four bytes are the zero-padded version of each byte that shows up in the hidraw output. I'm not sure what the difference between the ...a6 and ...a7 parts are -- the HID Report Descriptor is not well-formed. It does look like the a6 part comes right before the 80, which seems to be at the beginning of the message. For hidraw, it looks like we could just read 8 bytes at once, and be done.>> If you are interested in taking a look at related code, these two drivers are probably good starting points: >> >> * https://github.com/networkupstools/nut/blob/libusb-1.0/drivers/nutdrv_atcl_usb.c >> >> * https://github.com/networkupstools/nut/blob/libusb-1.0/drivers/richcomm_usb.cI mention these two drivers because, as I recall, they are also for non-PDC HID devices. For portability, NUT tends to use libusb rather than the Linux-specific hiddev/hidraw APIs. We have a few options: * Use hidraw, which is only available on Linux, but is known to work with this device. This would require a bit of autoconf plumbing in order to prevent other platforms from building the new driver. * Use HIDAPI, a portability library written by Alan Ott (author of the hidraw.txt kernel documentation). Adds another dependency that we haven't used yet. * Use libhid, which involves detaching the hiddev/hidraw driver from the device. We do this all the time with PDC HID devices. It might take a little more experimenting to find the libusb command to send. NUT developers: anyone else want to weigh in here? I'm leaning towards the third option, but I'm open to suggestions.
Drew from Zhrodague
2017-Mar-19 14:56 UTC
[Nut-upsdev] [nut-upsuser] Copeland Engineering Dockmaster
On 3/11/17 9:15 AM, Charles Lepple wrote:> [moved to nut-upsdev while we figure out the protocol] > >> On Mar 9, 2017, at 11:22 AM, Drew from Zhrodague <drewzhrodague at zhrodague.net> wrote: >> >> I'm able to cat /dev/usb/hiddev0 and /dev/hidraw0 - I pipe this through hexdump and I get different types of data from each: >> > I was trying to reconcile this with your earlier information from usbhid-dump, and it looks like hexdump is swapping bytes:Couple of updates: [] I've spoken with one of the developers, and he's offered to share some technical documentation with me. I'll report here with whatever I can. He's on vacation in China. [] I found this hid-example.c program, which fetches the values from the unit, including a timer, and a current state. However, this example program pushes data into the unit (to test writing), which sets the timers to astronomical values. I am not sure the format it wants, I'm unable to actually set anything, and the timer seems to default to 'a3 minutes'. My C is rusty, but commenting-out the writing portion makes the data collection not work. [] After unloading the hid modules, I was able to get more details on the USB format, and I am trying to read documentation to understand what's going on. From lsusb -vvv: Report Descriptor: (length is 34) Item(Global): Usage Page, data= [ 0xa0 0xff ] 65440 (null) Item(Local ): Usage, data= [ 0xa5 ] 165 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Local ): Usage, data= [ 0xa6 ] 166 (null) Item(Local ): Usage, data= [ 0xa7 ] 167 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0xa9 ] 169 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x08 ] 8 Item(Main ): Output, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none 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 This is from the hid-example.c: Report Descriptor: 6 a0 ff 9 a5 a1 1 9 a6 9 a7 15 0 25 ff 75 8 95 8 81 2 9 a9 15 0 25 ff 75 8 95 8 91 2 c0 I welcome any advice or assistance - ideally I'd like to use a proper UPS software, rather than my own egregious use of bash. Thanks! -- Drew from Zhrodague drew at zhrodague.net
Charles Lepple
2017-Mar-19 22:49 UTC
[Nut-upsdev] [nut-upsuser] Copeland Engineering Dockmaster
On Mar 19, 2017, at 10:56 AM, Drew from Zhrodague <drewzhrodague at zhrodague.net> wrote:> > [] I've spoken with one of the developers, and he's offered to share some technical documentation with me. I'll report here with whatever I can. He's on vacation in China.Hopefully this documentation will lead to fewer surprises down the road.> [] I found this hid-example.c program, which fetches the values from the unit, including a timer, and a current state. However, this example program pushes data into the unit (to test writing), which sets the timers to astronomical values. I am not sure the format it wants, I'm unable to actually set anything, and the timer seems to default to 'a3 minutes'. My C is rusty, but commenting-out the writing portion makes the data collection not work.I keep finding the generic hid-example.c from the Linux kernel tree when searching - do you have one specifically for this device?> > [] After unloading the hid modules, I was able to get more details on the USB format, and I am trying to read documentation to understand what's going on. From lsusb -vvv: > > Report Descriptor: (length is 34) > Item(Global): Usage Page, data= [ 0xa0 0xff ] 65440 > (null) > Item(Local ): Usage, data= [ 0xa5 ] 165 > (null) > Item(Main ): Collection, data= [ 0x01 ] 1 > Application > Item(Local ): Usage, data= [ 0xa6 ] 166 > (null) > Item(Local ): Usage, data= [ 0xa7 ] 167 > (null) > Item(Global): Logical Minimum, data= [ 0x00 ] 0 > Item(Global): Logical Maximum, data= [ 0xff ] 255 > Item(Global): Report Size, data= [ 0x08 ] 8 > Item(Global): Report Count, data= [ 0x08 ] 8 > Item(Main ): Input, data= [ 0x02 ] 2 > Data Variable Absolute No_Wrap Linear > Preferred_State No_Null_Position Non_Volatile BitfieldThis is the decoded version of roughly the first half of the Report Descriptor bytes below. The HID Report Descriptor has an obscure stack-based approach to organizing data. Both collections and individual Input/Output/Feature items are "named" with Usage numbers, and the Usage Page is a way to set the top 16 bits of the Usage number. The problem here is that the Usage Page is 0xFFA0, and IDs beginning with 0xFF are vendor-specific. (For an UPS, this would be something like 0x84 for the Power Device Page. See /usr/share/misc/usb.ids after the VID/PID section.) I think this one byte (Report Size == 8 bits) for Usage 0xA6, and the other seven bytes (left over from Report Count == 8 x 8 bits) for usage 0xA7. Logical Minimum and Logical Maximum of 0 and 255 tell us that the bytes are unsigned, but that is typical for just passing things through the HID APIs. (A mouse would use signed values for the X and Y axes, for instance.)> Item(Local ): Usage, data= [ 0xa9 ] 169 > (null) > Item(Global): Logical Minimum, data= [ 0x00 ] 0 > Item(Global): Logical Maximum, data= [ 0xff ] 255 > Item(Global): Report Size, data= [ 0x08 ] 8 > Item(Global): Report Count, data= [ 0x08 ] 8 > Item(Main ): Output, data= [ 0x02 ] 2 > Data Variable Absolute No_Wrap Linear > Preferred_State No_Null_Position Non_Volatile Bitfield > Item(Main ): End Collection, data=noneThis is the part that is being written to - 0xA9 is an 8-byte output field. As with the Input items earlier, the microcontroller is free to interpret these bytes in any way, so there might be fields within that.> 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 10Exact values usually don't matter here, but 8 bytes is a typical wMaxPacketSize, especially for lower-speed USB devices.> This is from the hid-example.c: > > Report Descriptor: > 6 a0 ff 9 a5 a1 1 9 a6 9 a7 15 0 25 ff 75 8 95 8 81 2 9 a9 15 0 25 ff 75 8 95 8 91 2 c0 >I mistyped earlier:> * Use libhid, which involves detaching the hiddev/hidraw driver from the device. We do this all the time with PDC HID devices. It might take a little more experimenting to find the libusb command to send.should be "Use libusb..." - at one point, there was a libhid that used libusb, but part of that got absorbed into NUT. I started modifying one of the drivers to do the reads, but it may not be of much use until we understand what needs to be written, as you mentioned here:> but commenting-out the writing portion makes the data collection not work.