Custom HID descriptor API via BLE nRF52840

CircuitPython on hardware including Adafruit's boards, and CircuitPython libraries using Blinka on host computers.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
tomeku
 
Posts: 19
Joined: Wed Jul 21, 2021 6:51 am

Custom HID descriptor API via BLE nRF52840

Post by tomeku »

Hey there,
I would like to ask for help and direction, how to set up a custom HID descriptor via BLE (nRF52840), e.g. for the Telephony page (0x0B).

I have played around with the source code, just to understand how it works, however unsuccessful. Later, I have found out in the link below, that perhaps custom HID API will be supported in CircuitPython 7, is that correct?

https://learn.adafruit.com/customizing- ... id-devices

Could you please share some examples, that could help me get started? E.g. Modifying the Consumer control, or similar.
How to use the API, so I don't have to open the source code?

Thanks!
Cheers,
Tom

User avatar
tannewt
 
Posts: 3298
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

Post by tannewt »

I don't know of any Telephony page examples.

It's much easier for us to support when looking at the code. I'd recommend you post what you have so far and what error you are getting.

User avatar
tannewt
 
Posts: 3298
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

Post by tannewt »

I think you want to start from this example: https://github.com/adafruit/Adafruit_Ci ... _periph.py

To switch the HID descriptor, you provide it to the HIDService constructor: https://circuitpython.readthedocs.io/pr ... HIDService

We don't have any examples of this but the HID descriptor should be the same as USB.

User avatar
tomeku
 
Posts: 19
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

Post by tomeku »

Thanks, Tannewt, sure thing!
Ok, let's put the Telephone page aside for now, and let's focus on the custom HID gamepad example.

Here is a quick and dirty code, where I try to use the example taken from the tutorial, but adapting to BLE.
Tutorial: https://learn.adafruit.com/customizing- ... id-devices

Note: Other Adafruit BLE HID examples work fine.

code.py

Code: Select all

import time
import board
from digitalio import DigitalInOut, Direction
import adafruit_ble
from adafruit_ble.advertising import Advertisement
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.standard.hid import HIDService
from adafruit_ble.services.standard.device_info import DeviceInfoService
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode

# This is the custom gamepad descriptor, taken from the tutorial
GAMEPAD_REPORT_DESCRIPTOR = bytes((
    0x05, 0x01,  # Usage Page (Generic Desktop Ctrls)
    0x09, 0x05,  # Usage (Game Pad)
    0xA1, 0x01,  # Collection (Application)
    0x85, 0x01,  # Report ID (will be replaced at runtime)
    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
))

#here is how I try to call it, trying to adapt that for BLE (instead of the usb_hid class)
hid = HIDService(GAMEPAD_REPORT_DESCRIPTOR)


button = DigitalInOut(board.D12)
button.direction = Direction.INPUT

device_info = DeviceInfoService(software_revision=adafruit_ble.__version__,
                                manufacturer="Adafruit Industries")
advertisement = ProvideServicesAdvertisement(hid)
advertisement.appearance = 961
scan_response = Advertisement()
scan_response.complete_name = "CircuitPython HID test"

ble = adafruit_ble.BLERadio()
if not ble.connected:
    print("advertising...")
    ble.start_advertising(advertisement, scan_response)
else:
    print("already connected")
    print(ble.connections)

cc = ConsumerControl(hid.devices)

while True:
    while not ble.connected:
        pass
    print("Start typing:")

    while ble.connected:
        if not button.value:  
            print("Button up pressed!")
            cc.send(ConsumerControlCode.BRIGHTNESS_INCREMENT)
        time.sleep(0.2)

    ble.start_advertising(advertisement)
For this example, I have just modified the "consumer_control_code.py" so it matches the Usage Page of game pad.

Code: Select all

self._consumer_device = find_device(devices, usage_page=0x01, usage=0x05)
And I get this output...

Code: Select all

code.py output:
already connected
(<BLEConnection object at 20019720>,)
Traceback (most recent call last):
  File "code.py", line 66, in <module>
  File "/lib/adafruit_hid/consumer_control.py", line 43, in __init__
  File "/lib/adafruit_hid/consumer_control.py", line 64, in send
  File "/lib/adafruit_hid/consumer_control.py", line 84, in press
  File "/lib/adafruit_ble/services/standard/hid.py", line 204, in send_report
ValueError: Value length != required fixed length
It seems, the HIDService accepted the custom gamepad descriptor, however, I don't understand the error. Let me know please, if you need more context, that I could forget to share.

What would be the right approach?

Thank you!
Cheers,
Tom

User avatar
tannewt
 
Posts: 3298
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

Post by tannewt »

Unfortunately, I think you are on your own on this one. We haven't validated the BLE HID stuff with different descriptors and clearly there is a bug.

The code its in is probably: https://github.com/adafruit/Adafruit_Ci ... id.py#L190

The error is due to the BLE characteristic being setup at a fixed size and the report you are sending is a different size. I'm not sure why this is happening and don't have time to look into it.

I'd suggest that you work from the lower level and not use the keyboard library if you are trying to make something besides a keyboard. Instead, just craft the reports yourself.

User avatar
tomeku
 
Posts: 19
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

Post by tomeku »

Understand, no worries. Thank you, tannewt, anyway for your time looking into it. I will give it a try.

Could you give me at least guidance, how to call it from code.py? I am not sure how to adapt the code from the tutorial to the BLE HID syntax.

Many thanks,
Cheers,
Tom

User avatar
tannewt
 
Posts: 3298
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

Post by tannewt »

Unfortunately, I can't be very specific without doing it myself. My approach would be to start from a working BLE HID example and then migrating the descriptor and reports over.

User avatar
tomeku
 
Posts: 19
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

Post by tomeku »

Thanks.
I was able to modify it, however, still I have some doubts about few things because it looks different than the "Customizing USB Devices".

Here is my descriptor and how I use it:

Code: Select all

My_REPORT_DESCRIPTOR = bytes((
b"\x05\x0B" # Telephony
b"\x09\x05" # Usage (Headset)	
b"\xA1\x01" # Collection (Application)	
b"\x85\x02" #     Report ID (2)	
b"\x05\x0B" #     Usage Page (Telephony Devices)	
b"\x15\x00" #     Logical Minimum (0)	
b"\x25\x01" #     Logical Maximum (1)	
b"\x09\x20" #     Usage (Hook Switch)	
b"\x09\x97" #     Usage (Line BusyTone)	
b"\x75\x01" #     Report Size (1)	
b"\x95\x02" #     Report Count (2)	
b"\x81\x22" #     Input (Data,Var,Abs,NWrp,Lin,NPrf,NNul,Bit)	
b"\x09\x2F" #     Usage (Phone Mute)	
b"\x09\x21" #     Usage (Flash)	
b"\x09\x24" #     Usage (Redial)	
b"\x09\x50" #     Usage (Speed Dial)	
b"\x75\x01" #     Report Size (1)	
b"\x95\x04" #     Report Count (4)	
b"\x81\x06" #     Input (Data,Var,Rel,NWrp,Lin,Pref,NNul,Bit)	
b"\x09\x07" #     Usage (Programmable Button)	
b"\x05\x09" #     Usage Page (Button)	
b"\x75\x01" #     Report Size (1)	
b"\x95\x01" #     Report Count (1)	
b"\x81\x02" #     Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)	
b"\x05\x0B" #     Usage Page (Telephony Devices)	
b"\x09\x06" #     Usage (Telephony Key Pad)	
b"\xA1\x02" #     Collection (Logical)	
b"\x19\xB0" #         Usage Minimum (Phone Key 0)	
b"\x29\xBB" #         Usage Maximum (Phone Key Pound)	
b"\x15\x00" #         Logical Minimum (0)	
b"\x25\x0B" #         Logical Maximum (11)	
b"\x75\x04" #         Report Size (4)	
b"\x95\x01" #         Report Count (1)	
b"\x81\x00" #         Input (Data,Ary,Abs)	
b"\xC0" #     End Collection	
b"\x75\x01" #     Report Size (1)	
b"\x95\x05" #     Report Count (5)	
b"\x81\x01" #     Input (Cnst,Ary,Abs)	
b"\x05\x08" #     Usage Page (LEDs)	
b"\x15\x00" #     Logical Minimum (0)	
b"\x25\x01" #     Logical Maximum (1)	
b"\x09\x17" #     Usage (Off-Hook)	
b"\x09\x09" #     Usage (Mute)	
b"\x09\x18" #     Usage (Ring)	
b"\x09\x20" #     Usage (Hold)	
b"\x09\x21" #     Usage (Microphone)	
b"\x75\x01" #     Report Size (1)	
b"\x95\x05" #     Report Count (5)	
b"\x91\x22" #     Output (Data,Var,Abs,NWrp,Lin,NPrf,NNul,NVol,Bit)	
b"\x05\x0B" #     Usage Page (Telephony Devices)	
b"\x15\x00" #     Logical Minimum (0)	
b"\x25\x01" #     Logical Maximum (1)	
b"\x09\x9E" #     Usage (Ringer)	
b"\x75\x01" #     Report Size (1)	
b"\x95\x01" #     Report Count (1)	
b"\x91\x22" #     Output (Data,Var,Abs,NWrp,Lin,NPrf,NNul,NVol,Bit)	
b"\x75\x01" #     Report Size (1)	
b"\x95\x0A" #     Report Count (10)	
b"\x91\x01" #     Output (Cnst,Ary,Abs,NWrp,Lin,Pref,NNul,NVol,Bit)	
b"\xC0" # End Collection	
))
Here is how I pass it in...

Code: Select all

hid = HIDService(My_REPORT_DESCRIPTOR)
I also needed to modify the hid.py (class ReportIn), to avoid that issue, about fixed length, discussed previously.

Code: Select all

# max_length=max_length,
fixed_length=False,
Now, the code runs without the previously mentioned error, but I have no clue how to verify if it is working. I only receive reports in my Xcode app, but I am not sure if the report is having the right length, etc.

The tutorial provided also API for setting a specific attributes, like: "in_report_length", "out_report_length", etc..

Code: Select all

gamepad = usb_hid.Device(
    report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
    usage_page=0x01,           # Generic Desktop Control
    usage=0x05,                # Gamepad
    in_report_length=6,        # This gamepad sends 6 bytes in its report.
    out_report_length=0,       # It does not receive any reports.
    report_id_index=7,         # The report id is at byte 7 (counting from 0)
                               # in the report descriptor.
)
How could I set it up for BLE as well? Am I on a right track?

User avatar
tomeku
 
Posts: 19
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

Post by tomeku »

I read through more docs, and I guess, the custom HID descriptor should be initialized in boot.py. I will try again, and let you know.

User avatar
tomeku
 
Posts: 19
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

Post by tomeku »

Hey tannewt,
I have progressed a bit. The good thing is, that I made it successfully working via USB (custom HID descriptor).

The challenge I have now is, how to make it work the same way using BLE?
Is there some trick, how to pass the custom descriptor, when initializing the BLE hid service?


Here is, how I pass the devices into my driver (btw. this works great via USB cable)

Code: Select all

cdc = CustomDescriptorControl(usb_hid.devices)
However, here I struggle. How to make the BLE HID service inherit my custom descriptor, defined in boot.py.

Code: Select all

hid = HIDService()
Thank you,
Tom

Locked
Please be positive and constructive with your questions and comments.

Return to “Adafruit CircuitPython”