0

Custom HID descriptor API via BLE nRF52840
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Custom HID descriptor API via BLE nRF52840

by tomeku on Wed Jul 21, 2021 6:59 am

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-usb-devices-in-circuitpython/hid-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

tomeku
 
Posts: 10
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

by tannewt on Wed Jul 21, 2021 11:46 am

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.

tannewt
 
Posts: 2577
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

by tannewt on Wed Jul 21, 2021 11:58 am

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.

tannewt
 
Posts: 2577
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

by tomeku on Wed Jul 21, 2021 12:15 pm

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 | TOGGLE FULL SIZE
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 | TOGGLE FULL SIZE
self._consumer_device = find_device(devices, usage_page=0x01, usage=0x05)


And I get this output...
Code: Select all | TOGGLE FULL SIZE
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

tomeku
 
Posts: 10
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

by tannewt on Wed Jul 21, 2021 1:30 pm

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.

tannewt
 
Posts: 2577
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

by tomeku on Wed Jul 21, 2021 4:25 pm

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

tomeku
 
Posts: 10
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

by tannewt on Thu Jul 22, 2021 7:32 pm

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.

tannewt
 
Posts: 2577
Joined: Thu Oct 06, 2016 8:48 pm

Re: Custom HID descriptor API via BLE nRF52840

by tomeku on Sun Jul 25, 2021 10:24 am

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 | TOGGLE FULL SIZE
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 | TOGGLE FULL SIZE
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 | TOGGLE FULL SIZE
# 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 | TOGGLE FULL SIZE
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?

tomeku
 
Posts: 10
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

by tomeku on Sun Jul 25, 2021 4:14 pm

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.

tomeku
 
Posts: 10
Joined: Wed Jul 21, 2021 6:51 am

Re: Custom HID descriptor API via BLE nRF52840

by tomeku on Sun Aug 01, 2021 4:28 pm

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 | TOGGLE FULL SIZE
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 | TOGGLE FULL SIZE
hid = HIDService()


Thank you,
Tom

tomeku
 
Posts: 10
Joined: Wed Jul 21, 2021 6:51 am

Please be positive and constructive with your questions and comments.