hidapi
hidapi copied to clipboard
hidapi documentation about number of bytes written and read
Discusison here:
- https://github.com/libusb/hidapi/issues/478#issuecomment-1592131366
- https://github.com/libusb/hidapi/issues/478#issuecomment-1592887588
As @todbot mentioned, this has been an issue bugging him. Same for me.
Testing device: Circuit Python rawhid example, no report IDs.
- https://github.com/adafruit/Adafruit_CircuitPython_HID/issues/115#issuecomment-1587535723
boot.py
import usb_hid
RAWHID_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xFF, # Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, # Usage (0x01)
0xA1, 0x01, # Collection (Application)
0x09, 0x02, # Usage (0x02)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8)
0x95, 0x40, # Report Count (64)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x03, # Usage (0x03)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8)
0x95, 0x40, # Report Count (64)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, # End Collection
))
raw_hid = usb_hid.Device(
report_descriptor=RAWHID_REPORT_DESCRIPTOR,
usage_page=0xFF00,
usage=0x01,
report_ids=(0,),
in_report_lengths=(64,),
out_report_lengths=(64,),
)
usb_hid.enable((raw_hid,))
code.py
import usb_hid
import time
d = usb_hid.devices[0]
while True:
report = bytearray(64) # must be same size as specified in HID Report Descriptor in boot.py
report[0] = 1
report[1] = 2
report[2] = 3
report[3] = 4
report[63] = 64
d.send_report(report)
time.sleep(1)
print(d.get_last_received_report())
hidapitester output under Windows, it says 65 bytes written
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 65 bytes:
01 02 03 04 05 06 07 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Closing device
hidapitester output under Linux (Ubuntu 20.04 x64): it says 64 bytes written.
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester --vidpid 2e8a:102e --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
01 02 03 04 05 06 07 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Closing device
hidapitester output under macOS 13.4 (Mac Mini M1): it says 64 bytes written.
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid 2e8a:102e --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
01 02 03 04 05 06 07 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Closing device```
hidtest info about the device (using Linux to get the real HID report descriptors)
Device Found type: 2e8a 102e path: /dev/hidraw3 serial_number: DE6185100F4D6522 Manufacturer: VCC-GND Studio Product: YD-RP2040 Release: 100 Interface: 3 Usage (page): 0x1 (0xff00) Bus type: 1 (USB)
Report Descriptor: (34 bytes) 0x06, 0x00, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x02, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x91, 0x02, 0xc0,
Relevant discussion before, which fixed the reported Input Report number of reading bytes
- https://github.com/libusb/hidapi/issues/229
- https://github.com/libusb/hidapi/pull/232
PR #232 is for sure correct, since the original code wrongly assumes the buffer does not contain the report ID.
Probably similar patch is requrired for Output report.
The Windows behavior is not wrong per se, Windows HID driver indeed adds the report ID 0 (no report ID) as the first byte in the buffer. However the report ID 0 is not really send on the USB bus. Since HIDAPI is a cross-platform library, I think we should make Windows to behave the same as Linux and macOS. Therefore we should not report 65 bytes in the above example for Windows, but rather 64 bytes which are the number of bytes the device really received.
When there is repot ID, Windows will still require the first byte to be the report ID, which is transmitted on the USB Bus.
Idealy we should always report the number of bytes transmitted on the USB bus, no matter there is a report ID or not. In that case, we will get the same number of bytes, for HID Input report, Output report and Feature report.
Test using Jan Axelson's generic HID example here. http://janaxelson.com/hidpage.htm
Windows reported 3 for Feature report writing and reading, one byter longer than the real device (2 bytes Input report, Output Report and Feature report, no report IDs). And same for Output report, Windows reports 3 bytes written.
Only for Input report, Windows is correct to report 2 bytes have been read, thanks to PR #232.
WIndows also always requires to send report ID 0 for the Output report for devices without report ID (and this causes the extra one byte length reported for the write), whereas Linux does not.
//Class specific descriptor - HID
// Defines 2-byte Input, Output, and Feature reports with vendor-defined data.
ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={
{
0x06, 0xA0, 0xFF, // Usage page (vendor defined)
0x09, 0x01, // Usage ID (vendor defined)
0xA1, 0x01, // Collection (application)
// The Input report
0x09, 0x03, // Usage ID - vendor defined
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x02, // Report Count (2 fields)
0x81, 0x02, // Input (Data, Variable, Absolute)
// The Output report
0x09, 0x04, // Usage ID - vendor defined
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x02, // Report Count (2 fields)
0x91, 0x02, // Output (Data, Variable, Absolute)
// The Feature report
0x09, 0x05, // Usage ID - vendor defined
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x02, // Report Count (2 fields)
0xB1, 0x02, // Feature (Data, Variable, Absolute)
0xC0} // end collection
};
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
01 02 00
Closing device
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 3 bytes:
01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
02 00
Closing device
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 01 02
Closing device
But in this case, hidapitester under Linux has the same behavior in terms of feature report as Windows.
And we have already a commit similar to PR #232 for Feature report. So maybe the device really has a three bytes Feature report.
https://github.com/libusb/hidapi/commit/6fcb0bb2282dbb505561a53689eba0c0536570d9
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
01 02 00
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
01 02
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 01 02
Closing device
So I am pretty sure we need to fix number of bytes written Output Report under Windows, with all my test results in #478.
I am not so sure about Feature report yet since Linux behaves the same as Windows.
Current implementation under Windows. It mentions that Windows expects the number of bytes which are in the longest report (plus one for the report number) bytes even if the data is a report which is shorter than that.
I think we need to report the length minus one, at least for the case when there is no report ID.
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
{
DWORD bytes_written = 0;
int function_result = -1;
BOOL res;
BOOL overlapped = FALSE;
unsigned char *buf;
if (!data || !length) {
register_string_error(dev, L"Zero buffer/length");
return function_result;
}
register_string_error(dev, NULL);
/* Make sure the right number of bytes are passed to WriteFile. Windows
expects the number of bytes which are in the _longest_ report (plus
one for the report number) bytes even if the data is a report
which is shorter than that. Windows gives us this value in
caps.OutputReportByteLength. If a user passes in fewer bytes than this,
use cached temporary buffer which is the proper size. */
if (length >= dev->output_report_length) {
/* The user passed the right number of bytes. Use the buffer as-is. */
buf = (unsigned char *) data;
} else {
if (dev->write_buf == NULL)
dev->write_buf = (unsigned char *) malloc(dev->output_report_length);
buf = dev->write_buf;
memcpy(buf, data, length);
memset(buf + length, 0, dev->output_report_length - length);
length = dev->output_report_length;
}
res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol);
if (!res) {
if (GetLastError() != ERROR_IO_PENDING) {
/* WriteFile() failed. Return error. */
register_winapi_error(dev, L"WriteFile");
goto end_of_function;
}
overlapped = TRUE;
}
if (overlapped) {
/* Wait for the transaction to complete. This makes
hid_write() synchronous. */
res = WaitForSingleObject(dev->write_ol.hEvent, 1000);
if (res != WAIT_OBJECT_0) {
/* There was a Timeout. */
register_winapi_error(dev, L"hid_write/WaitForSingleObject");
goto end_of_function;
}
/* Get the result. */
res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/);
if (res) {
function_result = bytes_written;
}
else {
/* The Write operation failed. */
register_winapi_error(dev, L"hid_write/GetOverlappedResult");
goto end_of_function;
}
}
end_of_function:
return function_result;
}
@JoergAtGithub
I think you have very good understanding of the issue and the necessary test devices. Please take a look at this issue as well when you got the chance.
@Youw
I think for Feature report, Linux hidraw implemenation is kind of the same as Windows. So that probably explains why I see the same behavior under Windows and Linux, since we just report the buffer length (without minus one for the case when there is no repor ID) and not the data on the USB bus.
Commits https://github.com/libusb/hidapi/commit/6fcb0bb2282dbb505561a53689eba0c0536570d9 is for sure correct, since the original code wrongly assues the buffer does not contain the Feature Report ID.
Now the question is whether we need to minus 1 for the case of no Report ID, to reflect the data on the USB Bus, and not the OS driver.
https://www.kernel.org/doc/Documentation/hid/hidraw.txt
HIDIOCSFEATURE(len): Send a Feature Report This ioctl will send a feature report to the device. Per the HID specification, feature reports are always sent using the control endpoint. Set the first byte of the supplied buffer to the report number. For devices which do not use numbered reports, set the first byte to 0. The report data begins in the second byte. Make sure to set len accordingly, to one more than the length of the report (to account for the report number).
HIDIOCGFEATURE(len): Get a Feature Report This ioctl will request a feature report from the device using the control endpoint. The first byte of the supplied buffer should be set to the report number of the requested report. For devices which do not use numbered reports, set the first byte to 0. The report will be returned starting at the first byte of the buffer (ie: the report number is not returned).
As of now, hidapitester using hidapi-libusb (custom build) behaves pretty same as hidapi-hidraw. So that is a good thing.
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
01 02 00
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
01 02
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 01 02
Closing device
@returns This function returns the number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error. Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
As per HIDAPI documentation, we should include the 0 report ID in the return. Is it included?
0 is a placeholder and not a Report ID. This is because the HID class specification specifies "If a Report ID tag was used in the Report descriptor, all reports include a single byte ID prefix. If the Report ID tag was not used, all values are returned in a single report and a prefix ID is not included in that report." and further about the representation in the ReportDescriptor "Set Report ID to 0 (zero) if Report IDs are not used."
Therefore only values from 1 to 255 can be Report IDs. The value 0 can be used in the ReportDescriptor, to define that the reports do not contain a ReportID.
Windows implements it as specified in the class specification and adds the Report ID prefix byte only for devices that use Report IDs.
I know that we need to keep backward compatibility, but from a clean API I would expect a clear seperation between address and data in two arguments.
I know that we need to keep backward compatibility, but from a clean API I would expect a clear seperation between address and data in two arguments.
@JoergAtGithub
Just wondering what you mean by the above. Are you proposing some new APIs? I think that will be okay if there are real benefits. We can always keep the older APIs (and in the future to deprecate them in HIDAPI 1.x version if necessary).
Just tested under macOS (13.4, Mac Mini M1) and the behavior is the same as Linux. So only Windows is out for the Output Report.
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
01 02 00
Closing device
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
01 02
Closing device
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 01 02
Closing device
Just wondering what you mean by the above.
I believe that is a though process "out loud", i.e. "it is probably would be clean to have an API where we send the report ID as a separate argument and only the report body in the buffer".
I think that will not change anything in terms of this issue. We still have to fight with different drivers/OS implementation to match the behavior the way we want. And because as per current documentation - we should always count the report ID as part of the data returned - having it as a separate argument would essentially mean -1 to the result.
But it will complicate some backend implementations, e.g. hidraw, which expects a single buffer as well. And if report ID will be a separate argument - we would have to make an intermediate buffer/copy before we can pass it into the underlying API.
But pre-pending the 0 byte is also an operation, which requires intermediate buffer/copy.
That's exactly my point. Having report ID at the beginning of the buffer - means not extra buffer "prepending" on HIDAPI side.
If you want to unify the behaviour between platforms you need either to prepend the 0 byte on Windows send functions for devices that don't use a Report ID or you need to remove the 0 byte for the other backends.
Because we have send and receive functions, there will be always a case, where you have to prepend the 0.
Performance wise I would say receiving reports is a more critical operation than sending.
That's exactly my point. Having report ID at the beginning of the buffer - means not extra buffer "prepending" on HIDAPI side.
There are two aspects of the issue.
1. Buffer handling -- platform specific.
I think the current codes already do that. For example, Windows codes have already done that, at least for Output Report and Feature Report (first byte of the report is the report ID, 0 when there is no report ID). And I believe all platforms are doing that for Feature report, based on my testing results.
I tend to believe the current codes are already fine in this aspect, i.e, we do not have issues in terms of getting thngs done (the device will receive the right data, host will also receive the right data).
2. Read/Write lentgh reporting
I think this is the issue now. It is not consistent.
Two ways of reporting -- now we are mixing the two. a) lengh of the buffer used by underlying OS driver -- Feature Report (including the 0 byte for device without Report ID). Currently I believe there is no behaviour differences for Feature Report across platforms.
b) length of the data bytes appearing on the USB Bus -- Input Report (not including the 0 byte for device without Report ID). For Windows, this could be due to the way we handle Input Report differently from Output Report and Feature report. Currently I believe there is no behaviour differences for Input Report across platforms.
c) mix of the two ways -- Output Report (Windows always including the 0 byte for device without Report ID, other platforms may or may not including). So Windows may report one byte more than the other platforms or may report the same length as the other platforms. I think we should at least fix this one.
3. Potential Solution for Issue 2. a) If we really want to unify all platforms in terms of the read/write length report mechanism, then I tend to htink Option b is the way to go for all three types of reports. But then it may require some jobs across all platforms and for Output and Feature reports..
b) If we just want to unify the behavior across platforms, then we only need to fix Output Report write length reporting to the user. I tend to think we only need to make Windows consistent with others. So only one platform (Windows) and one report type (Output Report) needs to be fixed.
If we just want to unify the behavior across platforms, then we only need to fix Output Report write length reporting to the user. I tend to think we only need to make Windows consistent with others. So only one platform (Windows) and one report type (Output Report) needs to be fixed.
+1
If we just want to unify the behavior across platforms, then we only need to fix Output Report write length reporting to the user. I tend to think we only need to make Windows consistent with others. So only one platform (Windows) and one report type (Output Report) needs to be fixed.
+1
Okay, I agree this is the way to go to keep backwards compatibillity and with minimum changes.
We may also need to improve the documentation as well.
I will try to compare the Linux hidraw implementation versus Windows implementation.
Let's talk about INPUT Report first.
/** @brief Read an Input report from a HID device with timeout.
Input reports are returned
to the host through the INTERRUPT IN endpoint. The first byte will
contain the Report number if the device uses numbered reports.
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into.
@param length The number of bytes to read. For devices with
multiple reports, make sure to read an extra byte for
the report number.
@param milliseconds timeout in milliseconds or -1 for blocking wait.
@returns
This function returns the actual number of bytes read and
-1 on error.
Call hid_error(dev) to get the failure reason.
If no packet was available to be read within
the timeout period, this function returns 0.
*/
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
/** @brief Get a input report from a HID device.
Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0)
Set the first byte of @p data[] to the Report ID of the
report to be read. Make sure to allow space for this
extra byte in @p data[]. Upon return, the first byte will
still contain the Report ID, and the report data will
start in data[1].
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into, including
the Report ID. Set the first byte of @p data[] to the
Report ID of the report to be read, or set it to zero
if your device does not use numbered reports.
@param length The number of bytes to read, including an
extra byte for the report ID. The buffer can be longer
than the actual report.
@returns
This function returns the number of bytes read plus
one for the report ID (which is still in the first
byte), or -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);
One thing hidapitester does not test is to use hid_get_input_report to get input report using Control Transfer. It only uses hid_read_timeout. So this may hide the issues with regard to Input Report.
1) Linux hidraw and Input Report
Linux hidraw docuemtation shows there will be no discrepancies using read method in hid_read_timeout and using HIDIOCSINPUT ioctl in hid_get_input_report.
read method: For devices which do not use numbered reports, the report data will begin at the first byte.
HIDIOCGINPUT ioctl: For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer. However, this contradicts the HIDAPI documentation for hid_get_input_report.
2) Windows HID API and Input Report
Windows hid_read_timeout implementation uses ReadFile which should give the same behavior as above.
Windows hid_get_input_report uses IOCTL_HID_GET_INPUT_REPORT, which will add the report ID 0 for devices which do not use numbered reports (no Report ID)
So we will have a discrepancy here within the Windows implementations betwen using hid_read_timeout and hid_read`. But this seems to be consistent with the HIDAPI documentation. This is quite confusing
int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
{
/* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */
return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length);
}
By the way, Windows hid_get_report has the same behavior as the above hid_get_input_report. It is mainly used for Feature Report (which is consistent with other platforms) but it is not used by Windows hid_get_input_report implementations in the end. Actually we can use it for hid_get_input_report without the following codes which is correct for Feature report. Then it will be consistent with hid_read.
/* When numbered reports aren't used,
bytes_returned seem to include only what is actually received from the device
(not including the first byte with 0, as an indication "no numbered reports"). */
if (data[0] == 0x0) {
bytes_returned++;
}
Reference for Windows https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidclass/ni-hidclass-ioctl_hid_get_input_report
IOCTL_HID_GET_INPUT_REPORT IOCTL (hidclass.h)
Input buffer The Parameters.DeviceIoControl.OutputBufferLength member specifies the size of a requester-allocated output buffer in bytes. The HID class driver uses this buffer to return an input report.
If the collection includes report IDs, the requester must set the first byte of the output buffer to a nonzero report ID. Otherwise, the requester must set the first byte of the output buffer to zero.
Input buffer length The size of the buffer in bytes. The buffer must be large enough to hold the input report plus one additional byte that specifies a nonzero report ID. If report ID is not used, the ID value is zero.
Reference for Linux: https://docs.kernel.org/hid/hidraw.html
read() read() will read a queued report received from the HID device. On USB devices, the reports read using read() are the reports sent from the device on the INTERRUPT IN endpoint. By default, read() will block until there is a report available to be read. read() can be made non-blocking, by passing the O_NONBLOCK flag to open(), or by setting the O_NONBLOCK flag using fcntl().
On a device which uses numbered reports, the first byte of the returned data will be the report number; the report data follows, beginning in the second byte. For devices which do not use numbered reports, the report data will begin at the first byte.
write() The write() function will write a report to the device. For USB devices, if the device has an INTERRUPT OUT endpoint, the report will be sent on that endpoint. If it does not, the report will be sent over the control endpoint, using a SET_REPORT transfer.
The first byte of the buffer passed to write() should be set to the report number. If the device does not use numbered reports, the first byte should be set to 0. The report data itself should begin at the second byte.
HIDIOCSFEATURE(len): Send a Feature Report
This ioctl will send a feature report to the device. Per the HID specification, feature reports are always sent using the control endpoint. Set the first byte of the supplied buffer to the report number. For devices which do not use numbered reports, set the first byte to 0. The report data begins in the second byte. Make sure to set len accordingly, to one more than the length of the report (to account for the report number).
HIDIOCGFEATURE(len): Get a Feature Report
This ioctl will request a feature report from the device using the control endpoint. The first byte of the supplied buffer should be set to the report number of the requested report. For devices which do not use numbered reports, set the first byte to 0. The returned report buffer will contain the report number in the first byte, followed by the report data read from the device. For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer.
HIDIOCSINPUT(len): Send an Input Report
This ioctl will send an input report to the device, using the control endpoint. In most cases, setting an input HID report on a device is meaningless and has no effect, but some devices may choose to use this to set or reset an initial state of a report. The format of the buffer issued with this report is identical to that of HIDIOCSFEATURE.
HIDIOCGINPUT(len): Get an Input Report
This ioctl will request an input report from the device using the control endpoint. This is slower on most devices where a dedicated In endpoint exists for regular input reports, but allows the host to request the value of a specific report number. Typically, this is used to request the initial states of an input report of a device, before an application listens for normal reports via the regular device read() interface. The format of the buffer issued with this report is identical to that of HIDIOCGFEATURE.
HIDIOCSOUTPUT(len): Send an Output Report
This ioctl will send an output report to the device, using the control endpoint. This is slower on most devices where a dedicated Out endpoint exists for regular output reports, but is added for completeness. Typically, this is used to set the initial states of an output report of a device, before an application sends updates via the regular device write() interface. The format of the buffer issued with this report is identical to that of HIDIOCSFEATURE.
HIDIOCGOUTPUT(len): Get an Output Report
This ioctl will request an output report from the device using the control endpoint. Typically, this is used to retrieve the initial state of an output report of a device, before an application updates it as necessary either via a HIDIOCSOUTPUT request, or the regular device write() interface. The format of the buffer issued with this report is identical to that of HIDIOCGFEATURE.
For Feature Report:
/** @brief Send a Feature report to the device.
Feature reports are sent over the Control endpoint as a
Set_Report transfer. The first byte of @p data[] must
contain the Report ID. For devices which only support a
single report, this must be set to 0x0. The remaining bytes
contain the report data. Since the Report ID is mandatory,
calls to hid_send_feature_report() will always contain one
more byte than the report contains. For example, if a hid
report is 16 bytes long, 17 bytes must be passed to
hid_send_feature_report(): the Report ID (or 0x0, for
devices which do not use numbered reports), followed by the
report data (16 bytes). In this example, the length passed
in would be 17.
@ingroup API
@param dev A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send, including
the report number.
@returns
This function returns the actual number of bytes written and
-1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
/** @brief Get a feature report from a HID device.
Set the first byte of @p data[] to the Report ID of the
report to be read. Make sure to allow space for this
extra byte in @p data[]. Upon return, the first byte will
still contain the Report ID, and the report data will
start in data[1].
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into, including
the Report ID. Set the first byte of @p data[] to the
Report ID of the report to be read, or set it to zero
if your device does not use numbered reports.
@param length The number of bytes to read, including an
extra byte for the report ID. The buffer can be longer
than the actual report.
@returns
This function returns the number of bytes read plus
one for the report ID (which is still in the first
byte), or -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
1) Linux hidraw for Feature Report
Linux hidraw uses the following IOCTLs for Sending and Receiving Feature Report.
The Linux hidraw documentation seems to indicate that Linux HIDAPI hid_get_feature_report implementation will not match the documentation, but that does not seem to be the case from my testing results. Strange.
https://docs.kernel.org/hid/hidraw.html HIDIOCSFEATURE(len): Send a Feature Report
This ioctl will send a feature report to the device. Per the HID specification, feature reports are always sent using the control endpoint. Set the first byte of the supplied buffer to the report number. For devices which do not use numbered reports, set the first byte to 0. The report data begins in the second byte. Make sure to set len accordingly, to one more than the length of the report (to account for the report number).
HIDIOCGFEATURE(len): Get a Feature Report
This ioctl will request a feature report from the device using the control endpoint. The first byte of the supplied buffer should be set to the report number of the requested report. For devices which do not use numbered reports, set the first byte to 0. The returned report buffer will contain the report number in the first byte, followed by the report data read from the device. For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer.
2) Windows:
From the documentations and testing results, Windows implementation matches the HIDAPI documentation.
hid_send_feature_report uses HidD_SetFeature function.
hid_get_feature_report uses IOCTL_HID_GET_FEATURE
3) Refererence for Windows
1) HidD_SetFeature function (hidsdi.h) https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidsdi/nf-hidsdi-hidd_setfeature
[in] ReportBuffer
Pointer to a caller-allocated feature report buffer that the caller uses to specify a HID report ID.
For more information about this parameter, see the Remarks section.
[in] ReportBufferLength
The size of the report buffer in bytes. The report buffer must be large enough to hold the feature report plus one additional byte that specifies a nonzero report ID. If report ID is not used, the ID value is zero.
2) IOCTL_HID_GET_FEATURE IOCTL (hidclass.h)
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidclass/ni-hidclass-ioctl_hid_get_feature IOCTL_HID_GET_FEATURE IOCTL (hidclass.h)
Input buffer The Parameters.DeviceIoControl.OutputBufferLength member specifies the size, in bytes, of a requester-allocated output buffer. The HID class driver uses this buffer to return a feature report.
If the collection includes report IDs, the requester must set the first byte of the output buffer to a nonzero report ID. Otherwise, the requester must set the first byte of the output buffer to zero.
Input buffer length The size of the buffer in bytes. The buffer must be large enough to hold the feature report plus one additional byte that specifies a nonzero report ID. If report ID is not used, the ID value is zero.
For Output Report:
/** @brief Write an Output report to a HID device.
The first byte of @p data[] must contain the Report ID. For
devices which only support a single report, this must be set
to 0x0. The remaining bytes contain the report data. Since
the Report ID is mandatory, calls to hid_write() will always
contain one more byte than the report contains. For example,
if a hid report is 16 bytes long, 17 bytes must be passed to
hid_write(), the Report ID (or 0x0, for devices with a
single report), followed by the report data (16 bytes). In
this example, the length passed in would be 17.
hid_write() will send the data on the first OUT endpoint, if
one exists. If it does not, it will send the data through
the Control Endpoint (Endpoint 0).
@ingroup API
@param dev A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send.
@returns
This function returns the actual number of bytes written and
-1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
1) Linux hidraw implementations.
From the documentation it seems to be in line with the above documentation. However, in testing, it seems to support both uses cases -- to ignore the report ID 0 or not to ignore the report ID 0 for devices without Report ID.
https://docs.kernel.org/hid/hidraw.html write() The write() function will write a report to the device. For USB devices, if the device has an INTERRUPT OUT endpoint, the report will be sent on that endpoint. If it does not, the report will be sent over the control endpoint, using a SET_REPORT transfer.
The first byte of the buffer passed to write() should be set to the report number. If the device does not use numbered reports, the first byte should be set to 0. The report data itself should begin at the second byte.
2) Windows implementation
Even though I mentioned that Windows may report one more byte than the other platforms, I tend to think it is actually according to the HIDAPI documentation.
From the comments: using Windows WriteFile API.
/* Make sure the right number of bytes are passed to WriteFile. Windows
expects the number of bytes which are in the _longest_ report (plus
one for the report number) bytes even if the data is a report
which is shorter than that. Windows gives us this value in
caps.OutputReportByteLength. If a user passes in fewer bytes than this,
use cached temporary buffer which is the proper size. */
Take note we do not use either HidD_SetOutputReport function or IOCTL_HID_SET_OUTPUT_REPORT due to their limitations. But they will also have the same behaviors.
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidsdi/nf-hidsdi-hidd_setoutputreport
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidclass/ni-hidclass-ioctl_hid_set_output_report
Summary:
It seems to me HIDAPI Windows implementation is actually consistent with the HIDAPI documentation, even though the behavior seems to be a bit different from the other platforms, at least for the Output Reports.
To do:
- To test
hid_get_input_report
I will try to use a modified version of hidapitester.
- https://github.com/todbot/hidapitester/issues/4
-
To understand more about hidraw implementation to see if I can figure out the discrepancies between test results and hidraw documentation.
-
To check HIDAPI libusb implementaiton -- I believe it matches the hidraw implementation based on testing results.
To understand more about hidraw implementation to see if I can figure out the discrepancies between test results and hidraw documentation.
My conclusion: I am guessing that Linux hidraw documentations on HIDIOCGFEATURE and HIDIOCGINPUT are wrong. And there are no issues in HIDAPI Linux hidraw implementations. This will explain why Linux hidraw is consistent with the Windows implementation in testing results, for both Input Report and Feature Report.
- Existing discussions about Feature Report.
Confusions about hid_get_feature_report
https://github.com/libusb/hidapi/issues/229#issuecomment-810780640
https://github.com/liquidctl/liquidctl/issues/312
- I am guessing that It could be that Linux hidraw
HIDIOCGFEATUREdocumentation is wrong and it actually will add report ID 0 for devices without Report ID. If that is the case, then it can explain why I get the same results from hidraw and libusb under Linux.
From Linux kernel source code, I think indeed this is the case.
https://github.com/torvalds/linux/blob/master/drivers/hid/hidraw.c
/*
* This function performs a Get_Report transfer over the control endpoint
* per section 7.2.1 of the HID specification, version 1.1. The first byte
* of buffer is the report number to request, or 0x0 if the device does not
* use numbered reports. The report_type parameter can be HID_FEATURE_REPORT
* or HID_INPUT_REPORT.
*/
static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type)
...
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
break;
}
...
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
break;
}
...
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
break;
}
-
I am also guessing that Linux hidraw
HIDIOCGINPUTdocumentation is also wrong, from the above source codes, it actually will add report ID 0 for devices without Report ID. -
The following are for references about writing Output report and Feature Report. No documentation issues from hidraw in this case.
/*
* The first byte of the report buffer is expected to be a report number.
*/
static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type)
...
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
break;
}
...
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
break;
}
...
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
break;
}
- To test
hid_get_input_reportI will try to use a modified version of hidapitester.
- https://github.com/todbot/hidapitester/issues/4
Windows testing results with device with Report ID.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42,0x43 --read-input 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
02 41 42
Reading 3-byte input report 1, 2000 msec timeout...read 3 bytes:
01 41 42
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42,0x43 --read-input-alt 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
02 41 42
Reading 3-byte input report, report_id 1...
read 3 bytes:
01 41 42
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 3,0x31,0x32 --read-feature 4
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
03 31 32
Reading 3-byte feature report, report_id 4...read 3 bytes:
04 31 32
Closing device
Windows testing results with device without numbered Report ID.
Unfortunately the added command --read-input-alt does not seem to work with Device without Report ID.
Windows must send report ID 0 for Output Report. If not, it will not work as expected.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 3 bytes: (automatically adding report ID 0)
41 42
Reading 2-byte input report 0, 2000 msec timeout...read 2 bytes:
42 00
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 41 42
Reading 3-byte input report 0, 2000 msec timeout...read 2 bytes:
41 42 00
Closing device
Windows must send report ID 0 for Output Report. If not, it will not work as expected.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 3 bytes: (automatically adding report ID 0)
41 42
Reading 2-byte input report, report_id 0...
read -1 bytes: (must read 3 bytes)
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 41 42
Reading 3-byte input report, report_id 0...
read 3 bytes:
00 00 00 (data not correct)
Closing device
Windows must send report ID 0 for Feature Report. If not, it will not work as expected.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 2 --send-feature 0x31.0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote 2 bytes: (adding 0 and then discard 0x32)
31 00
Reading 2-byte feature report, report_id 0...read -1 bytes: (must read 3 bytes)
00 00
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,0x32,0x33 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 32 33
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 32 33
Closing device
My modification:
diff --git a/hidapitester.c b/hidapitester.c
index de9d268..64f7b03 100644
--- a/hidapitester.c
+++ b/hidapitester.c
@@ -40,6 +40,7 @@ static void print_usage(char *myname)
" --read-feature <reportId> Read Feature report (w/ reportId, 0 if unused) \n"
" --send-output <datalist> Send Ouput report to device \n"
" --read-input [reportId] Read Input report (w/ opt. reportId, if unused)\n"
+" --read-input-alt [reportId] Read Input report using hidapi hid_get_input_report (w/ opt. reportId, if unused)\n"
" --read-input-forever [rId] Read Input reports in a loop forever \n"
" --length <len>, -l <len> Set buffer length in bytes of report to send/read\n"
" --timeout <msecs> Timeout in millisecs to wait for input reads \n"
@@ -86,6 +87,7 @@ enum {
CMD_SEND_OUTPUT,
CMD_SEND_FEATURE,
CMD_READ_INPUT,
+ CMD_READ_INPUT_ALT,
CMD_READ_FEATURE,
CMD_READ_INPUT_FOREVER,
};
@@ -206,10 +208,11 @@ int main(int argc, char* argv[])
{"send-output", required_argument, &cmd, CMD_SEND_OUTPUT},
{"send-out", required_argument, &cmd, CMD_SEND_OUTPUT},
{"send-feature", required_argument, &cmd, CMD_SEND_FEATURE},
- {"read-input", optional_argument, &cmd, CMD_READ_INPUT},
- {"read-in", optional_argument, &cmd, CMD_READ_INPUT},
+ {"read-input", required_argument, &cmd, CMD_READ_INPUT},
+ {"read-input-alt", required_argument, &cmd, CMD_READ_INPUT_ALT},
+ {"read-in", required_argument, &cmd, CMD_READ_INPUT},
{"read-feature", required_argument, &cmd, CMD_READ_FEATURE},
- {"read-input-forever", optional_argument, &cmd, CMD_READ_INPUT_FOREVER},
+ {"read-input-forever", required_argument, &cmd, CMD_READ_INPUT_FOREVER},
{NULL,0,0,0}
};
char* shortopts = "vht:l:qb:";
@@ -396,6 +399,32 @@ int main(int argc, char* argv[])
}
} while( cmd == CMD_READ_INPUT_FOREVER );
}
+
+ else if( cmd == CMD_READ_INPUT_ALT ) {
+
+ if( !dev ) {
+ msg("Error on read: no device opened.\n"); break;
+ }
+ if( !buflen) {
+ msg("Error on read: buffer length is 0. Use --len to specify.\n");
+ break;
+ }
+ uint8_t report_id = (optarg) ? strtol(optarg,NULL,10) : 0;
+ buf[0] = report_id;
+ msg("Reading %d-byte input report, report_id %d... \n",buflen, report_id);
+
+ res = hid_get_input_report(dev, buf, buflen);
+ msg("read %d bytes:\n", res);
+ if( res > 0 ) {
+ printbuf(buf,buflen, print_base, print_width);
+ memset(buf,0,buflen); // clear it out
+ }
+ else if( res == -1 ) { // removed device
+ cmd = CMD_CLOSE;
+ break;
+ }
+ }
+
else if( cmd == CMD_READ_FEATURE ) {
if( !dev ) {
Linux hidraw test results
1) For devices with numbered report ID
Results are consistent with Windows.
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
02 41 42
Reading 3-byte input report 1, 2000 msec timeout...read 3 bytes:
01 41 42
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input-alt 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
02 41 42
Reading 3-byte input report, report_id 1...
read 3 bytes:
01 41 42
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 3,0x41,0x42 --read-feature 4
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
03 41 42
Reading 3-byte feature report, report_id 4...read 3 bytes:
04 41 42
Closing device
2) For devices without numbered report ID.
Result are not always consistent with Windows. But if we follow HIDAPI documentatation exactly, it seems to be consistent.
(For Output Report, report ID 0 can be sent or not sent, both cases are working, just there is one byte difference in the number of bytes written)
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
41 42
Reading 2-byte input report 0, 2000 msec timeout...read 2 bytes:
41 42
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 41 42
Reading 3-byte input report 0, 2000 msec timeout...read 2 bytes:
41 42 00
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
41 42
Reading 2-byte input report, report_id 0...
read 2 bytes:
00 00 (data not correct)
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 41 42
Reading 3-byte input report, report_id 0...
read 3 bytes:
00 00 00 (data not correct)
Closing device
(For Feature Report, report ID 0 must be sent, if not will get error)
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 0,0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 31 32
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 31 32
Closing device
(For Feature Report, report ID 0 must be sent, if not will get error)
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-feature 0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote -1 bytes:
31 32
Reading 2-byte feature report, report_id 0...read 2 bytes:
00 31
Closing device
To check HIDAPI libusb implementaiton -- I believe it matches the hidraw implementation based on testing results.
Linux libusb test results
1) For devices with numbered report ID
Results are consistent with Windows
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
02 41 42
Reading 3-byte input report 1, 2000 msec timeout...read 3 bytes:
01 41 42
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input-alt 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
02 41 42
Reading 3-byte input report, report_id 1...
read 3 bytes:
01 41 42
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 3,0x41,0x42 --read-feature 4
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
03 41 42
Reading 3-byte feature report, report_id 4...read 3 bytes:
04 41 42
Closing device
2) For devices without numbered report ID. Results are consistent with hidraw backend.
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 0,0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 31 32
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 31 32
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-feature 0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote -1 bytes:
31 32
Reading 2-byte feature report, report_id 0...read 2 bytes:
00 31
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
41 42
Reading 2-byte input report, report_id 0...
read 2 bytes:
00 00 (data not correct)
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
00 41 42
Reading 3-byte input report, report_id 0...
read 3 bytes:
00 00 00 (data not correct)
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 0,0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
00 31 32
Reading 3-byte feature report, report_id 0...read 3 bytes:
00 31 32
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-feature 0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote -1 bytes:
31 32
Reading 2-byte feature report, report_id 0...read 2 bytes:
00 31
Closing device
@Youw and @todbot
I need some help on using hid_get_input_report with hidapitester. I think I need to implement some types of time out. Currently it returns (I believe) the correct number of bytes reading but the data are not correct (all 0).
@Youw
Somehow based on the test results, if I follow exactly the existing HIDAPI documentation, there are no issues, Windows implementations are consistent with Linux hidraw and Linux libusb implementations.
It is just that Linux hidraw and libusb implementations are more flexible in terms of output reports.
So in the end, maybe there is nothing to fix.
And I think it is not crticial that hidapi is a bit lax in terms of reporting to the user number of bytes written, as long as the device gets the right data.
But this still pending the confirmation from the above hid_get_input_report.
Edit: there is one thing to fix about documentation for hid_send_feature_report.
/** @brief Send a Feature report to the device.
Feature reports are sent over the Control endpoint as a
Set_Report transfer. The first byte of @p data[] must
contain the Report ID. For devices which only support a
single report, this must be set to 0x0. The remaining bytes
contain the report data. Since the Report ID is mandatory,
calls to hid_send_feature_report() will always contain one
more byte than the report contains. For example, if a hid
report is 16 bytes long, 17 bytes must be passed to
hid_send_feature_report(): the Report ID (or 0x0, for
devices which do not use numbered reports), followed by the
report data (16 bytes). In this example, the length passed
in would be 17.
@ingroup API
@param dev A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send, including
the report number.
@returns
**This function returns the actual number of bytes written** and
-1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
The last part is not correct. It should be changed to the following.
@returns
This function returns the actual number of bytes written plus
one for the report ID (which is still in the first byte), and
-1 on error.
Call hid_error(dev) to get the failure reason.
@todbot and @JoergAtGithub
If possible, please test on your end to see if you come to the same conclusion as I or not. Thanks.
My conclusion is based on my test devices and it could be totally wrong if the firmware has issues.
@jonasmalacofilho
Confusions about hid_get_feature_report https://github.com/libusb/hidapi/issues/229#issuecomment-810780640 https://github.com/liquidctl/liquidctl/issues/312
You mentioned the following.
However, the same text appears in
hid_get_feature_reportwhich, in turn, seems to have conflicting implementations:
If my conclusion is correct that Linux hidraw documentation about HIDIOCGFEATURE is incorrect, then your confusion will be cleared.
Now we need to confirm with Linux HID developer side.
Since you are also very familiar with hidapi and hidraw, please check on your side as well under Linux. Thanks.
@Youw and @todbot
I need some help on using
hid_get_input_reportwithhidapitester. I think I need to implement some types of time out. Currently it returns (I believe) the correct number of bytes reading but the data are not correct (all 0).
I think my modification is working even though it is a quick and dirty fix. It turns out I did not modify Jan Axeson's original HID FW correctly for devices without numbered report ID. I have fixed that for report length <=63 Bytes. I still have one issue with the FW for Report length = 64 Bytes but I will try to fix that later.
Here are the results under Windows and I can see that Windows implementation is as per the documentation, using the FW without numbered report ID and report length = 63 Bytes.
We can see that hid_read_timeout gets 63 bytes and hid_get_input_report gets 64 bytes under Windows, which is as per the HIDAPI documentation. Whether that is a good thing or not is another story.
ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={
{
0x06, 0xA0, 0xFF, // Usage page (vendor defined)
0x09, 0x01, // Usage ID (vendor defined)
0xA1, 0x01, // Collection (application)
// The Input report
0x09, 0x03, // Usage ID - vendor defined
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x3f, // Report Count (63 fields)
0x81, 0x02, // Input (Data, Variable, Absolute)
// The Output report
0x09, 0x04, // Usage ID - vendor defined
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x3f, // Report Count (63 fields)
0x91, 0x02, // Output (Data, Variable, Absolute)
// The Feature report
0x09, 0x05, // Usage ID - vendor defined
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x3f, // Report Count (63 fields)
0xB1, 0x02, // Feature (Data, Variable, Absolute)
0xC0} // end collection
};
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report 0, 4000 msec timeout...read 63 bytes:
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20
21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 00
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report, report_id 0...
read 64 bytes:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-feature 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 64-byte feature report...wrote 64 bytes:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte feature report, report_id 0...read 64 bytes:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E B1 0B 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device