Comments (29)
You can use "raw HID" and write your own report descriptor, but it's not part of the library. Unusual uses of HID could be done in another library. We try to keep this particular library small so it fits on all possible boards.
https://learn.adafruit.com/custom-hid-devices-in-circuitpython
https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/hid-devices#custom-hid-devices-3096614
from adafruit_circuitpython_hid.
I can verify that "rawhid" code that works on QTPY RP2040 does not work on QTPY M0 SAMD21 with the "boot_out.txt" message of ValueError: report_ids length must be <= 1
.
It seems the SAMD21 usb_hid
can only support one report, while the RP2040 one can support more than one. That is, changing the above code to be a single report works on SAMD21
# boot.py
import usb_hid
REPORT_COUNT = 63 # size of report in bytes
CUSTOM_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xff, # Usage page (Vendor Defined Page1)
0x09, 0x01, # Usage (Vendor Page 1)
0xA1, 0x01, # Collection (Application)
0x85, 0x02, # Report ID (2)
0x09, 0x00, # Usage (Undefined)
0x09, 0x00, # Usage Page (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits)
0x95, REPORT_COUNT, # Report Count (64 fields)
0x82, 0x02, 0x01, # Input (Data,Var,Abs,Buf)
0x92, 0x02, 0x01, # Output (Data,Var,Abs,Buf)
0xC0, # End Collection
))
raw_hid = usb_hid.Device(
report_descriptor=CUSTOM_REPORT_DESCRIPTOR,
usage_page=0xff00, # Vendor defined
usage=0x01, # Vendor page 1
report_ids=(2,),
in_report_lengths=(REPORT_COUNT,),
out_report_lengths=(REPORT_COUNT,),
)
usb_hid.enable( (raw_hid,) )
# code.py
import time
import usb_hid
import adafruit_hid
raw_hid = adafruit_hid.find_device(usb_hid.devices, usage_page=0xff00, usage=0x01)
print("raw_hid: %04x %04x" % (raw_hid.usage_page, raw_hid.usage) )
while True:
out_report = raw_hid.get_last_received_report(2) # out from computer
if out_report:
print("len:",len(out_report),["%02x" % x for x in out_report])
time.sleep(0.5)
print("sending copy on reportid 2")
in_report = bytearray(out_report) # copy in case we want to modify
raw_hid.send_report(in_report, 2); # in to computer
from adafruit_circuitpython_hid.
OK, yes, this is because of circuitpython/ports/atmel-samd/mpconfigport.h
:
// Only support simpler HID descriptors on SAMD21.
#define CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR (1)
The default is 6
.
This was to save flash space on the SAMD21 builds: adafruit/circuitpython#5272. This should be documented in a Limitations section.
from adafruit_circuitpython_hid.
Feature report lengths
The PR does not show how to specify feature_report_length.
For a feature report, use the same length in in_report_length
and out_report_length
. Internally, these specify buffers to allocate.
Maximum report lengths
Another strange thing, @todbot and I are both puzzled why changing
REPORT_COUNT = 64
will lead to problem for reading back the Input Report. It seems the library might have an issue with devices with Report IDs
The wMaxPacketSize for the compose HID device is set to 64 bytes here. I think this may be limiting the report size?
https://github.com/adafruit/circuitpython/blob/main/shared-module/usb_hid/__init__.c#L39
...
#define HID_IN_ENDPOINT_INDEX (20)
0x03, // 21 bmAttributes (Interrupt)
0x40, 0x00, // 22,23 wMaxPacketSize 64
0x08, // 24 bInterval 8 (unit depends on device speed)
0x07, // 25 bLength
0x05, // 26 bDescriptorType (Endpoint)
0xFF, // 27 bEndpointAddress (OUT/H2D) [SET AT RUNTIME]
#define HID_OUT_ENDPOINT_INDEX (27)
0x03, // 28 bmAttributes (Interrupt)
0x40, 0x00, // 29,30 wMaxPacketSize 64
0x08, // 31 bInterval 8 (unit depends on device speed)
Feature reports possible bug.
**** Someone just found a potential issue with TinyUSB and feature ports, and proposed a PR fix, which has not yet been reviewed: hathach/tinyusb#2119. This may have something to do with any feature report issues you are seeing.
OUT Report report_id heuristic
There is currently a heuristic about the report_id used for OUT reports
https://github.com/adafruit/circuitpython/blob/main/shared-module/usb_hid/Device.c#L300.
I think this should work for you anyway, due to the way the heuristic works.
The underlying problem is something that TinyUSB could fix and is an open issue:
hathach/tinyusb#1990
if (report_id == 0 && report_type == HID_REPORT_TYPE_INVALID) {
// This could be a report with a non-zero report ID in the first byte, or
// it could be for report ID 0.
// Heuristic: see if there's a device with report ID 0, and if its report length matches
// the size of the incoming buffer. In that case, assume the first byte is not the report ID,
// but is data. Otherwise use the first byte as the report id.
if (usb_hid_get_device_with_report_id(0, &hid_device, &id_idx) &&
hid_device &&
hid_device->out_report_buffers[id_idx] &&
hid_device->out_report_lengths[id_idx] == bufsize) {
// Use as is, with report_id 0.
} else {
// No matching report ID 0, so use the first byte as the report ID.
report_id = buffer[0];
buffer++;
bufsize--;
}
} else if (report_type != HID_REPORT_TYPE_OUTPUT && report_type != HID_REPORT_TYPE_FEATURE) {
return;
}
from adafruit_circuitpython_hid.
The example gamepad descriptor may not work on macOS. If I remember right, I could not produce a gamepad report descriptor that worked across Windows, Linux, and macOS (or maybe iOS?) all at once. That is one reason we dropped GamePad from the library.
from adafruit_circuitpython_hid.
Thanks. I will close this issue then.
from adafruit_circuitpython_hid.
For now I use Adafruit_TinyUSB_Arduino instead.
https://github.com/adafruit/Adafruit_TinyUSB_Arduino/tree/master/examples/HID/hid_generic_inout
from adafruit_circuitpython_hid.
The following boot.py code seems to work, at least for USB HID emulation.
import usb_hid
# This is only one example of a gamepad descriptor, and may not suit your needs.
CUSTOM_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xff, # Usage page (Vendor Defined Page1)
0x09, 0x01, # Usage (Vendor Page 1)
0xA1, 0x01, # Collection (Application)
0x85, 0x01, # Report ID (1)
0x09, 0x00, # Usage (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits or 1 byte)
0x95, 0x40, # Report Count (64)
0x82, 0x02, 0x01, # Input (Data,Var,Abs,Buf)
0x85, 0x02, # Report ID (2)
0x09, 0x00, # Usage (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits or 1 byte)
0x95, 0x40, # Report Count (64)
0x92, 0x02, 0x01, # Output (Data,Var,Abs,Buf)
0xC0, # End Collection
))
custom_device = usb_hid.Device(
report_descriptor=CUSTOM_REPORT_DESCRIPTOR,
usage_page=0xff00, # Vendor defined
usage=0x01, # Vendor page 1
report_ids=(1, 2),
in_report_lengths=(64,0),
out_report_lengths=(0, 64),
)
usb_hid.enable(
(usb_hid.Device.CONSUMER_CONTROL, # strange that I need to keep one of the three default ones to get this to work
custom_device)
)
from adafruit_circuitpython_hid.
However, sending input report does not work. Maybe there is a simple fix to the following code.py
.
import time
import usb_hid
import adafruit_hid
custom_device = adafruit_hid.find_device(usb_hid.devices, usage_page=0xff00, usage=0x01)
print("custom_device:", custom_device)
while True:
report = bytearray(64) # must be same size as specified in HID Report Descriptor in boot.py
report = custom_device.get_last_received_report(2)
print(["%02x" % x for x in report])
time.sleep(1)
report[0] = 1
custom_device.send_report(report, 1)
hidapitester output
(py310x64venv) PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --usagePage=0xff00 --usage 1 --open --list-detail
Opening device, vid/pid:0x2E8A/0x102E, usagePage/usage: FF00/1
Device opened
2E8A/102E: VCC-GND Studio - CircuitPython HID
vendorId: 0x2E8A
productId: 0x102E
usagePage: 0xFF00
usage: 0x0001
serial_number: DE6185100F4D6522
interface: 3
path: \\?\HID#VID_2E8A&PID_102E&MI_03&Col02#8&15f7af61&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
Closing device
(py310x64venv) PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --usagePage=0xff00 --usage 1 --open --send-output 2,3,4,5 --read-input 1
Opening device, vid/pid:0x2E8A/0x102E, usagePage/usage: FF00/1
Device opened
Writing output report of 64-bytes...wrote 65 bytes:
02 03 04 05 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 00 00 00 00
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Closing device
from adafruit_circuitpython_hid.
usb_hid.enable(
(usb_hid.Device.CONSUMER_CONTROL, # strange that I need to keep one of the three default ones to get this to work
custom_device)
Do you mean that usb_hid.enable((custom_device,))
does not work? How does it not work?
Note that the argument must be a tuple (so it must have a comma).
However, sending input report does not work.
Do you mean sending from the CircuitPython device? .send_report()
will prefix the report with the specified report id. You don't need to do that youself. (Is that what you are doing with report[0] = 1
?)
from adafruit_circuitpython_hid.
Do you mean that
usb_hid.enable((custom_device,))
does not work? How does it not work? Note that the argument must be a tuple (so it must have a comma).
Thanks for the help, this works now.
usb_hid.enable(
(custom_device,)
)
Adafruit CircuitPython 8.1.0 on 2023-05-22; VCC-GND Studio YD RP2040 with rp2040
Board ID:vcc_gnd_yd_rp2040
UID:DE6185100F4D6522
boot.py output:
from adafruit_circuitpython_hid.
Do you mean sending from the CircuitPython device? .send_report() will prefix the report with the specified report id. You don't need to do that youself. (Is that what you are doing with report[0] = 1 ?)
I see. Thanks. But it still does not work.
I have changed the report size to be smaller (4 bytes) and the following code still does not work for the input report (from CircuitPython to host).
boot.py
import usb_hid
# This is only one example of a gamepad descriptor, and may not suit your needs.
CUSTOM_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xff, # Usage page (Vendor Defined Page1)
0x09, 0x01, # Usage (Vendor Page 1)
0xA1, 0x01, # Collection (Application)
0x85, 0x01, # Report ID (1)
0x09, 0x00, # Usage (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits)
0x95, 0x04, # Report Count (4 fields)
0x82, 0x02, 0x01, # Input (Data,Var,Abs,Buf)
0x85, 0x02, # Report ID (2)
0x09, 0x00, # Usage (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits)
0x95, 0x04, # Report Count (4 fields)
0x92, 0x02, 0x01, # Output (Data,Var,Abs,Buf)
0xC0, # End Collection
))
custom_device = usb_hid.Device(
report_descriptor=CUSTOM_REPORT_DESCRIPTOR,
usage_page=0xff00, # Vendor defined
usage=0x01, # Vendor page 1
report_ids=(1, 2),
in_report_lengths=(4,0),
out_report_lengths=(0,4),
)
usb_hid.enable(
(custom_device,)
)
code.py
import time
import usb_hid
import adafruit_hid
custom_device = adafruit_hid.find_device(usb_hid.devices, usage_page=0xff00, usage=0x01)
print("custom_device:", custom_device)
while True:
report = bytearray(4) # must be same size as specified in HID Report Descriptor in boot.py
# report[0] = 1
report[1] = 21
report[2] = 22
report[3] = 23
# report[4] = 24
custom_device.send_report(report, 1)
time.sleep(1)
report = custom_device.get_last_received_report(2)
print(["%02x" % x for x in report])
hidapitester output
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --usagePage=0xff00 --usage 1 --length 4 --open --send-output 2,3,4,5,6
Opening device, vid/pid:0x2E8A/0x102E, usagePage/usage: FF00/1
Device opened
Writing output report of 4-bytes...wrote 5 bytes:
02 03 04 05
Closing device
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --usagePage=0xff00 --usage 1 --length 4 --open --read-input
Opening device, vid/pid:0x2E8A/0x102E, usagePage/usage: FF00/1
Device opened
Reading 5-byte input report 0, 250 msec timeout...read 0 bytes:
Closing device
from adafruit_circuitpython_hid.
Another thing, the above boot.py
does not work on Sam D21.
Adafruit CircuitPython 8.1.0 on 2023-05-22; SparkFun SAMD21 Mini Breakout with samd21g18
Board ID:sparkfun_samd21_mini
UID:7CA3AF314A34555020312E3436260FFF
boot.py output:
Traceback (most recent call last):
File "boot.py", line 35, in <module>
ValueError: report_ids length must be <= 1
from adafruit_circuitpython_hid.
BTW, this is part of my efforts to create test devices for HIDAPI project.
from adafruit_circuitpython_hid.
This PR fixed "Raw HID" support, and was tested with no report ID's. See the test program in the PR posts. You might find it helpful:
adafruit/circuitpython#7806
from adafruit_circuitpython_hid.
Another thing, the above
boot.py
does not work on Sam D21.
This is strange -- I don't see any code in the CircuitPython implementation that would generate a <=
error. Could you double-check that boot.py
is exactly the same on that board?
from adafruit_circuitpython_hid.
This PR fixed "Raw HID" support, and was tested with no report ID's. See the test program in the PR posts. You might find it helpful: adafruit/circuitpython#7806
Yes that example works very well. I have added an INPUT report (from device to host) and it works as well on the Raspberry Pi Pico. Tested under Windows 11 x64 and macOS 13.4 (Mac Mini M1, ARM64).
No change to 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,))
#usb_hid.enable((usb_hid.Device.KEYBOARD,))
Minor changes to code.py to enable INPUT report (from device to host).
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 11
- OUTPUT report is okay
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
Serial monitor output.
None
b'\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
None
None
- INPUT report is also working.
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --open --length 64 --read-input-forever
Opening device, vid/pid: 0x2E8A/0x102E
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 64 bytes:
01 02 03 04 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 00 00 00 40
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 64 bytes:
01 02 03 04 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 00 00 00 40
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 64 bytes:
01 02 03 04 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 00 00 00 40
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...
from adafruit_circuitpython_hid.
And the code works fine with SAMD21 as well.
Adafruit CircuitPython 8.1.0 on 2023-05-22; SparkFun SAMD21 Mini Breakout with samd21g18
Board ID:sparkfun_samd21_mini
UID:7CA3AF314A34555020312E3436260FFF
boot.py output:
hidapitester output
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 1b4f:8d22 --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x1B4F/0x8D22
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
PS C:\work\hid\hidapitester> .\hidapitester --vidpid 1b4f:8d22 --open --length 64 --read-input-forever
Opening device, vid/pid: 0x1B4F/0x8D22
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 64 bytes:
01 02 03 04 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 00 00 00 40
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 64 bytes:
01 02 03 04 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 00 00 00 40
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 64 bytes:
01 02 03 04 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 00 00 00 40
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
Reading 64-byte input report 0, 250 msec timeout...read 0 bytes:
from adafruit_circuitpython_hid.
Another thing, the above
boot.py
does not work on Sam D21.This is strange -- I don't see any code in the CircuitPython implementation that would generate a
<=
error. Could you double-check thatboot.py
is exactly the same on that board?
Indeed this is strange. The code is exactly the same.
import usb_hid
# This is only one example of a gamepad descriptor, and may not suit your needs.
CUSTOM_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xff, # Usage page (Vendor Defined Page1)
0x09, 0x01, # Usage (Vendor Page 1)
0xA1, 0x01, # Collection (Application)
0x85, 0x01, # Report ID (1)
0x09, 0x00, # Usage (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits)
0x95, 0x04, # Report Count (4 fields)
0x82, 0x02, 0x01, # Input (Data,Var,Abs,Buf)
0x85, 0x02, # Report ID (2)
0x09, 0x00, # Usage (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits)
0x95, 0x04, # Report Count (4 fields)
0x92, 0x02, 0x01, # Output (Data,Var,Abs,Buf)
0xC0, # End Collection
))
custom_device = usb_hid.Device(
report_descriptor=CUSTOM_REPORT_DESCRIPTOR,
usage_page=0xff00, # Vendor defined
usage=0x01, # Vendor page 1
report_ids=(1, 2),
in_report_lengths=(4,0),
out_report_lengths=(0,4),
)
usb_hid.enable(
(custom_device,)
)
Adafruit CircuitPython 8.1.0 on 2023-05-22; SparkFun SAMD21 Mini Breakout with samd21g18
Board ID:sparkfun_samd21_mini
UID:7CA3AF314A34555020312E3436260FFF
boot.py output:
Traceback (most recent call last):
File "boot.py", line 35, in <module>
ValueError: report_ids length must be <= 1
from adafruit_circuitpython_hid.
So I use your boot.py and just adding the report ID, SAMD21 will fail.
import usb_hid
RAWHID_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xFF, # Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, # Usage (0x01)
0xA1, 0x01, # Collection (Application)
0x85, 0x01, # Report ID (1)
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)
0x85, 0x02, # Report ID (2)
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=(1,2),
in_report_lengths=(64,0),
out_report_lengths=(0,64),
)
usb_hid.enable((raw_hid,))
#usb_hid.enable((usb_hid.Device.KEYBOARD,))
Adafruit CircuitPython 8.1.0 on 2023-05-22; SparkFun SAMD21 Mini Breakout with samd21g18
Board ID:sparkfun_samd21_mini
UID:7CA3AF314A34555020312E3436260FFF
boot.py output:
Traceback (most recent call last):
File "boot.py", line 31, in <module>
ValueError: report_ids length must be <= 1
from adafruit_circuitpython_hid.
Thanks for the help.
Another strange thing, @todbot and I are both puzzled why changing REPORT_COUNT = 64
will lead to problem for reading back the Input Report. It seems the library might have an issue with devices with Report IDs.
With REPORT_COUNT = 63
the code by @todbot works perfectly.
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid=2e8a:102e --open -l 64 --send-output 2,3,4,5 --timeout 1000 --read-input
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
02 03 04 05 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 00 00 00 00
Reading 64-byte input report 0, 1000 msec timeout...read 64 bytes:
02 03 04 05 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 00 00 00 00
Closing device
With REPORT_COUNT = 64
the code by @todbot does not work for the HID Input Report.
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid=2e8a:102e --open -l 65 --send-output 2,3,4,5 --timeout 1000 --read-input
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 65-bytes...wrote 65 bytes:
02 03 04 05 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 00 00 00 00
00
Reading 65-byte input report 0, 1000 msec timeout...read 0 bytes:
Closing device
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid=2e8a:102e --open -l 64 --send-output 2,3,4,5 --timeout 1000 --read-input
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
02 03 04 05 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 00 00 00 00
Reading 64-byte input report 0, 1000 msec timeout...read 0 bytes:
Closing device
from adafruit_circuitpython_hid.
Once removing the report ID, @todbot's code works fine with 64 bytes REPORTS_COUNT.
# boot.py
import usb_hid
REPORT_COUNT = 64 # size of report in bytes
CUSTOM_REPORT_DESCRIPTOR = bytes((
0x06, 0x00, 0xff, # Usage page (Vendor Defined Page1)
0x09, 0x01, # Usage (Vendor Page 1)
0xA1, 0x01, # Collection (Application)
# 0x85, 0x02, # Report ID (2)
0x09, 0x00, # Usage (Undefined)
0x09, 0x00, # Usage Page (Undefined)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x75, 0x08, # Report Size (8 bits)
0x95, REPORT_COUNT, # Report Count (64 fields)
0x82, 0x02, 0x01, # Input (Data,Var,Abs,Buf)
0x92, 0x02, 0x01, # Output (Data,Var,Abs,Buf)
0xC0, # End Collection
))
raw_hid = usb_hid.Device(
report_descriptor=CUSTOM_REPORT_DESCRIPTOR,
usage_page=0xff00, # Vendor defined
usage=0x01, # Vendor page 1
report_ids=(0,),
in_report_lengths=(REPORT_COUNT,),
out_report_lengths=(REPORT_COUNT,),
)
usb_hid.enable( (raw_hid,) )
# code.py
import time
import usb_hid
import adafruit_hid
raw_hid = adafruit_hid.find_device(usb_hid.devices, usage_page=0xff00, usage=0x01)
print("raw_hid: %04x %04x" % (raw_hid.usage_page, raw_hid.usage) )
while True:
out_report = raw_hid.get_last_received_report() # out from computer
if out_report:
print("len:",len(out_report),["%02x" % x for x in out_report])
time.sleep(0.5)
print("sending copy on reportid 0")
in_report = bytearray(out_report) # copy in case we want to modify
raw_hid.send_report(in_report); # in to computer
mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid=2e8a:102e --open -l 64 --send-output 2,3,4,5 --timeout 2000 --read-input
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
02 03 04 05 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 00 00 00 00
Reading 64-byte input report 0, 2000 msec timeout...read 64 bytes:
02 03 04 05 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 00 00 00 00
Closing device
from adafruit_circuitpython_hid.
Another thing, I do not see how feature report is supported.
The PR does not show how to specify feature_report_length.
For Feature report examples, you can refer to the following Arduino example by @todbot.
https://github.com/todbot/hidapitester/tree/master/test_hardware/hidtest_tinyusb
from adafruit_circuitpython_hid.
Hi there :)
I struggle with getting a USB HID gamepad up and running for macOS.
What I did:
- I have a Raspberry Pico
- I set up some On/Off Switches on the Pico
- I installed CircuitPython 8.x on the Pico
- I added the adafruit_hid library to the Pico and also the hid_gamepad.py from the examples
- I created a boot.py file on the Pico
- I added a bit of code for testing - currently just one single switch
boot.py
import usb_hid
# This is only one example of a gamepad descriptor, and may not suit your needs.
GAMEPAD_REPORT_DESCRIPTOR = bytes((
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x05, # Usage (Game Pad)
0xA1, 0x01, # Collection (Application)
0x85, 0x04, # Report ID (4)
0x05, 0x09, # Usage Page (Button)
0x19, 0x01, # Usage Minimum (Button 1)
0x29, 0x10, # Usage Maximum (Button 16)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x10, # Report Count (16)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x15, 0x81, # Logical Minimum (-127)
0x25, 0x7F, # Logical Maximum (127)
0x09, 0x30, # Usage (X)
0x09, 0x31, # Usage (Y)
0x09, 0x32, # Usage (Z)
0x09, 0x35, # Usage (Rz)
0x75, 0x08, # Report Size (8)
0x95, 0x04, # Report Count (4)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
))
gamepad = usb_hid.Device(
report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
usage_page=0x01, # Generic Desktop Control
usage=0x05, # Gamepad
report_ids=(4,), # Descriptor uses report ID 4.
in_report_lengths=(6,), # This gamepad sends 6 bytes in its report.
out_report_lengths=(0,), # It does not receive any reports.
)
usb_hid.enable(
(usb_hid.Device.KEYBOARD,
gamepad)
)
code.py
import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
from hid_gamepad import Gamepad
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
######## Keyboard, Mouse, GamePad
keyb = Keyboard(usb_hid.devices)
keyb_layout = KeyboardLayoutUS(keyb)
gp = Gamepad(usb_hid.devices)
# Create some buttons. The physical buttons are connected
# to ground on one side and these and these pins on the other.
button_pins = (board.GP0,)
gamepad_buttons = (1,)
buttons = [digitalio.DigitalInOut(pin) for pin in button_pins]
for button in buttons:
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP
while True:
# Buttons are grounded when pressed (.value = False).
for i, button in enumerate(buttons):
gamepad_button_num = gamepad_buttons[i]
if button.value:
gp.release_buttons(gamepad_button_num)
print("", gamepad_button_num, end=" ON")
time.sleep(0.25)
else:
gp.press_buttons(gamepad_button_num)
print("", gamepad_button_num, end=" OFF")
time.sleep(0.25)
Its is working fine so far (ignore the keyboard stuff, I do not need it at that time, just for testing.)
BUT: What I would expect is, that the pico now shows up as a USB HID device with one button/switch somewhere in the macOS Operating System Preferences (Gamepads?).
But it is not showing up anywhere. So has anyone some Experience with macOS 13 or 14 how "announce" the Pico as a Gamepad for usage?
(Background: I want to build a board of switches in order to build a cockpit interface for X-Plane)
Any hint appreciated :)
from adafruit_circuitpython_hid.
Ok, update on that...
I installed an App called "Controllers Lite" from the Mac App Store and the Pico is showing up and the button input is recognized.
So basically the HID interface is working fine...
Now I have to find an answer to the question, what has to be done, that the controller shows up in the macOS Preferences and also in x-plane...
Maybe a hint could be, that the above mentioned app recognizes the switch toggle as input from button 272 (ID: 280). Which is strange, as I declared for the HID Device to only have 16 Buttons... ?
from adafruit_circuitpython_hid.
The example gamepad descriptor may not work on macOS. If I remember right, I could not produce a gamepad report descriptor that worked across Windows, Linux, and macOS (or maybe iOS?) all at once. That is one reason we dropped GamePad from the library.
okay, but that means that it might be possible to get it running if one would know, which bytes are to be set in the descriptor, in order to make it work correctly with macOS?
But as its not documented anywhere its more a try and error approach... :/
from adafruit_circuitpython_hid.
I don't know of a working descriptor for macOS. In the past, we added gamepad here: adafruit/circuitpython#776, and then later removed it. The comments in that PR indicate that macOS could see the gamepad via (new URL) https://hardwaretester.com/gamepad.
It sounds like applications can see the gamepad, but there may be no associated Preferences. It might be System Information, and you can list USB devices with ioreg -p IOUSB
.
As for the button numbers, that is some mapping we don't have control over, and I think you will just need to try them to see the mapping. I don't know where that would be documented.
from adafruit_circuitpython_hid.
Thanks @dhalbert
I tried a few things but gave up.
I then gave the pico board a try, by using Arduino IDE and installed the boards-package for the RP2040.
I then used the Adafruit USB_HID library and the pico was immediately recognized by X-Plane as a joystick input device.
I managed to get everything to work.
I have no clue what they do differently...
I wanted to look into their code for the protocol, but was not able to find the C-libs for that part. Adafruit itself has only docs for python?
To be honest, I'd rather would use python top write my code. My Arduino C-skills are insufficient.
from adafruit_circuitpython_hid.
Closing this as it does not appear to be an issue specific to this library There are some TinyUSB improvements that would help in the long run.
from adafruit_circuitpython_hid.
Related Issues (20)
- ImportError: no module named '__future__' HOT 6
- Control Codes in keyboard_layout_us not supported HOT 4
- Examples using the keypad module for a keyboard and macros HOT 6
- Pico + keyboard + output to monochrome 20x4 LCD?
- Consumer Control not working HOT 3
- Support for zoom in/out
- adafruit_hid.keyboard.Keyboard.led_on only returns True once only HOT 2
- Keyboard press delay
- usb_hid behaviour at boot.. HOT 28
- Implement HID LampArray Support HOT 2
- BLE Keyboard Functionality Issue HOT 3
- BLE Keyboard Functionality Issue on EFR32xG24 Explorer Kit HOT 1
- Gamecontroller setup under mac HOT 7
- Add support for "Pan" or "Horizontal" wheel movement as well as Back/Forward Buttons
- `find_device` module missing in `adafruit_hid.keyboard.Keyboard` HOT 6
- Double key in Discord shortcuts HOT 2
- USB Keyboard no longer sends keystrokes after windows machine go to sleep HOT 14
- Possible error in Mouse movement library. HOT 2
- Control the speed of layout.write()? HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from adafruit_circuitpython_hid.