Coder Social home page Coder Social logo

Comments (29)

dhalbert avatar dhalbert commented on July 26, 2024 1

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.

todbot avatar todbot commented on July 26, 2024 1

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.

dhalbert avatar dhalbert commented on July 26, 2024 1

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.

dhalbert avatar dhalbert commented on July 26, 2024 1

@mcuee @todbot

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.

dhalbert avatar dhalbert commented on July 26, 2024 1

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.

mcuee avatar mcuee commented on July 26, 2024

Thanks. I will close this issue then.

from adafruit_circuitpython_hid.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

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.

dhalbert avatar dhalbert commented on July 26, 2024
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.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

BTW, this is part of my efforts to create test devices for HIDAPI project.

from adafruit_circuitpython_hid.

dhalbert avatar dhalbert commented on July 26, 2024

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.

dhalbert avatar dhalbert commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

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

  1. 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
  1. 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.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

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?

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.

mcuee avatar mcuee commented on July 26, 2024

@dhalbert

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.

mcuee avatar mcuee commented on July 26, 2024

@dhalbert

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.

mcuee avatar mcuee commented on July 26, 2024

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.

mcuee avatar mcuee commented on July 26, 2024

@dhalbert

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.

marschro avatar marschro commented on July 26, 2024

Hi there :)

I struggle with getting a USB HID gamepad up and running for macOS.

What I did:

  1. I have a Raspberry Pico
  2. I set up some On/Off Switches on the Pico
  3. I installed CircuitPython 8.x on the Pico
  4. I added the adafruit_hid library to the Pico and also the hid_gamepad.py from the examples
  5. I created a boot.py file on the Pico
  6. 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.

marschro avatar marschro commented on July 26, 2024

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.

marschro avatar marschro commented on July 26, 2024

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.

dhalbert avatar dhalbert commented on July 26, 2024

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.

marschro avatar marschro commented on July 26, 2024

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.

dhalbert avatar dhalbert commented on July 26, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.