Esp32-snippets: ESP32 BT-HID Device

Created on 26 Nov 2017  ·  216Comments  ·  Source: nkolban/esp32-snippets

Is it possible to use the ESP32_BLE_Arduino library to build a HID-Keyboard to send keystrokes to another device? My goal: I would like to connect a ESP32 board to a BT device like an iPad and send a space character. I found https://github.com/asterics/esp32_mouse_keyboard but can't figure out how to this running in Arduino IDE. Any help would be appreciated - I found a lot of users asking for a similar feature for the ESP32.

Most helpful comment

This works for me with windows 10. There is minor issue, is not sending capital H, but i have no time to check it why. Its with txt extension because github does not allow to send ino file.
HID_keyboard.ino.txt

All 216 comments

I'm afraid there is nothing yet in the BLE C++ classes that would make your request possible. Maybe a question posted to one of the forums here might give some positive results:

https://esp32.com/index.php

It is my loose understanding that HID support is based on Bluetooth classic (BR/EDR) as opposed to BLE and, so far, these classes only cover BLE functions.

Thank you very much @nkolban. I already found a project for esp-idf for HID and BT but I was hoping for an example for the arduino IDE. I have no experience with IDF and the tool chain and how to integrate those stuff with arduino. Maybe this will come in the future.

Do you have a link to the HID and BT project that uses ESP-IDF? I'd like to file that and have a look to see what it would take to eventually encapsulate that in the C++ libraries. That would be the first step towards an Arduino encapsulation.

The most complete and supported solution seems to be the BTStack port for the esp32 for IDF here
https://github.com/bluekitchen/btstack/tree/master/port/esp32 within this library there are various examples for HID devices and other BT applications.

OOOh thats cool. But scary as well. If I get a sense of that story, it is a replacement for the Bluetooth stack supplied by Espressif (which I believe is based on Bluedroid stack implementation).

Yes so this might be a problem to have both stacks available at the same time in an Arduino library. The example under https://github.com/asterics/esp32_mouse_keyboard uses the bt.h from the regular Espressif library so (see ble_hidd_demo_main.c).

New day, new lessons:
hid

My esp32 just become HID keyboard. ANd now its HID mouse:
mouse

Of course its not real keyboard and mouse, but my windows 10 is stupid and thinks it is ;)

@chegewara Have you build an Arduino project for that already? I would be interested :-)
Sending a keyboard key to any BT device like an iPAD would be a great use case for the ESP32.

Oh no, just fooled my laptop by setting appearance(). The rest of code has nothing to do with hid keyboard. But i may do some research and see if this is hard to do.

I have some good news and some bad news. Good news that work is in progress. Bad news is driver crash under windows with error code 0x0a(device cant start). I can say for sure what is the reason, but its a chance that hid keybord requires secure connection and we dont have implemented it yet.

My next step will be to rewrite code in esp-idf and see what happen and maybe add some security there.

EDIT more good news, my samsung s4 connects to esp32 hid keyboard, so now i have to find way to send some text

I have good news. Esp32 hid keyboard is connecting with my android phone and i can send text. This mean hid keyboard works, at least software part. Still is some work to do but we know it can be done.

@chegewara great work, would you share your current project to play around with it? I just need a running starting point for the Arduino IDE :-)

Im not sure yet, i will provide code or share links ive been using during research. Im thinking about writing blog article about this. For now maybe this will help you a bit:
https://github.com/I0x0I/DIY-A-BLE-Keyboard

https://docs.mbed.com/docs/ble-hid/en/latest/api/md_doc_HIDService.html

This is very important documentation if you want to play with HID devices. Appendix E gives some good look at what descriptors are required to setup hid device:
http://www.usb.org/developers/hidpage/HID1_11.pdf

Services and characteristics:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<configuration>
<service uuid="1800">
<description>Generic Access Profile</description>
<characteristic uuid="2a00">
<properties read="true" const="true"/>
<value>BGT Keyboard Demo</value>
</characteristic>
<characteristic uuid="2a01">
<properties read="true" const="true"/>
<value type="hex">c103</value>
</characteristic>
</service>
<service type="primary" uuid="180A" id="manufacturer">
<characteristic uuid="2A29">
<properties read="true" const="true"/>
<value>Bluegiga</value>
</characteristic>
<characteristic uuid="2A50">
<!--  PnP ID required by HID Profile  -->
<properties read="true" const="true"/>
<value type="hex">014700ffffffff</value>
</characteristic>
</service>
<service uuid="180f">
<description>Battery</description>
<characteristic uuid="2a19" id="xgatt_battery">
<properties read="true"/>
<value type="hex">32</value>
</characteristic>
</service>
<service uuid="1812" advertise="true">
<characteristic uuid="2a4d" id="hid_keyboard_in">
<!--  Keyboard input report  -->
<properties notify="true" read="true"/>
<value length="20" variable_length="true"/>
<descriptor uuid="2908">
<!--  Report reference id=0x00 type=0x01 (input)  -->
<properties read="true" const="true"/>
<value type="hex">0001</value>
</descriptor>
</characteristic>
<characteristic uuid="2a4d" id="hid_keyboard_out">
<!--  Keyboard output report  -->
<properties write="true" write_no_response="true" read="true"/>
<value length="20" variable_length="true"/>
<descriptor uuid="2908">
<!--  Report reference id=0x00 type=0x02 (output)  -->
<properties read="true" const="true"/>
<value type="hex">0002</value>
</descriptor>
</characteristic>
<characteristic uuid="2a4d" id="hid_keyboard_feature">
<!--  Keyboard feature report  -->
<properties write="true" read="true"/>
<value length="20" variable_length="true"/>
<descriptor uuid="2908">
<!--  Report reference id=0x00 type=0x03 (feature)  -->
<properties read="true" const="true"/>
<value type="hex">0003</value>
</descriptor>
</characteristic>
<characteristic uuid="2a4b">
<!--  Report map example from USB HID Specification  -->
<properties read="true" authenticated_read="true" const="true"/>
<value type="hex">
05010906A101050719E029E71500250175019508810295017508810195057501050819012905910295017503910195067508150025650507190029658100C0
</value>
</characteristic>
<characteristic uuid="2a4a">
<!--
 HID Information version=0x0100 countrycode=0x00 flags=0x02 (normally connectable) 
-->
<properties read="true" const="true"/>
<value type="hex">00010002</value>
</characteristic>
<characteristic uuid="2a4c" id="hid_control">
<!--  HID Control point, used to control suspending  -->
<properties write_no_response="true"/>
<value length="1"/>
</characteristic>
<characteristic uuid="2a22" id="hid_boot_keyboard_in">
<!--  Boot Keyboard input report  -->
<properties notify="true" read="true"/>
<value length="20" variable_length="true"/>
</characteristic>
<characteristic uuid="2a32" id="hid_boot_keyboard_out">
<!--  Boot Keyboard output report  -->
<properties write_no_response="true" read="true" write="true"/>
<value length="20" variable_length="true"/>
</characteristic>
<characteristic uuid="2a4e" id="hid_protocol_mode">
<!--  Protocol mode select  -->
<properties write_no_response="true" read="true"/>
<value length="1"/>
</characteristic>
</service>
</configuration>

@chegewara I managed to run the BlueKitchen ESP32 Port for ESP-IDF (https://github.com/bluekitchen/btstack/blob/master/example/hid_keyboard_demo.c) it runs on my ESP32 board and sends continuously text messages to may mac book and to my android phone as soon as you pair them. The whole setup for the ESP-IDF and libraries is quite complicated so I cannot use this process for teaching. This is why I was looking for a nice wrapper for the Arduino IDE so that you can fire up a example project, hit flash and project runs :-)

Can you wait few days, im still working on arduino code. At the moment im just sending random letter but i still missing one thing. My code requires that notifications to be turned on on client side (my android phone). At the moment only way to achieve this is to open nRF connect, connect to esp32 hid and turn on notifications.

This is what i did so far. There is still a lot to do but it should send random character in loop:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include <string>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

bool connected = false;
  BLEService *pService;
  BLEService *pService1;
  BLECharacteristic *reportChar1;
  BLECharacteristic *reportChar2;
  BLECharacteristic *reportChar3;
  class MyCallbacks : public BLEServerCallbacks {
    void onConnect(BLEServer* pServer){
      connected=true;
    }

    void onDisconnect(BLEServer* pServer){
      connected=false;
    }
  };

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("ESP32");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyCallbacks());
  pService = pServer->createService((uint16_t)0x180a);
  pService1 = pServer->createService((uint16_t)0x1812, 30);
  setupCharacteristics();

  pService->start();
  pService1->start();
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->setAppearance(961);
  pAdvertising->addServiceUUID((uint16_t)0x1812);
  pAdvertising->start();

  Serial.println("Characteristic defined! Now you can read it in your phone!");
}
uint8_t v[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};

void loop() {
  // put your main code here, to run repeatedly:
  delay(5000);
  if(connected){
    uint8_t a[] = {0x0, 0x0, random(0x04,0x26), 0x0,0x0,0x0,0x0,0x0};
    reportChar1->setValue(a,sizeof(a));
    reportChar1->notify();    

    reportChar1->setValue(v, sizeof(v));
    reportChar1->notify();
  }
}

void setupCharacteristics() {

  BLECharacteristic *manufacturer = pService->createCharacteristic(
                                       (uint16_t)0x2a29,
                                       BLECharacteristic::PROPERTY_READ 
                                     );
  std::string name = "espressif";
  manufacturer->setValue(name);

  BLECharacteristic *pnpIDChar = pService->createCharacteristic(
                                       (uint16_t)0x2a50,
                                       BLECharacteristic::PROPERTY_READ 
                                     );
  const uint8_t pnp[] = {0x01,0xe5,0x02,0xcd,0xab,0x01,0x00};
  pnpIDChar->setValue((uint8_t*)pnp, sizeof(pnp));

  BLECharacteristic *hidInfoChar = pService1->createCharacteristic(
                                       (uint16_t)0x2a4a,
                                       BLECharacteristic::PROPERTY_READ 
                                     );
  const uint8_t val1[] = {0x00,0x01,0x00,0x02};
  hidInfoChar->setValue((uint8_t*)val1, 4);

  BLECharacteristic *reportMapChar = pService1->createCharacteristic(
                                       (uint16_t)0x2a4b,
                                       BLECharacteristic::PROPERTY_READ 
                                     );
  const uint8_t val2[] = {
    0x05,0x01,0x09,0x06,0xA1,0x01,0x05,0x07,
    0x19,0xE0,0x29,0xE7,0x15,0x00,0x25,0x01,
    0x75,0x01,0x95,0x08,0x81,0x02,0x95,0x01,
    0x75,0x08,0x81,0x01,0x95,0x05,0x75,0x01,
    0x05,0x08,0x19,0x01,0x29,0x05,0x91,0x02,
    0x95,0x01,0x75,0x03,0x91,0x01,0x95,0x06,
    0x75,0x08,0x15,0x00,0x25,0x65,0x05,0x07,
    0x19,0x00,0x29,0x65,0x81,0x00,0xC0}; //TODO
  reportMapChar->setValue((uint8_t*)val2, sizeof(val2));

  BLECharacteristic *hidControlChar = pService1->createCharacteristic(
                                       (uint16_t)0x2a4c,
                                       BLECharacteristic::PROPERTY_WRITE_NR 
                                     );

  reportChar1 = pService1->createCharacteristic(
                                       (uint16_t)0x2a4d,
                                       BLECharacteristic::PROPERTY_READ   |
                                       BLECharacteristic::PROPERTY_NOTIFY                                       
                                     );
  BLEDescriptor *desc1 = new BLEDescriptor(BLEUUID((uint16_t)0x2908));
  const uint8_t desc1_val[] = {0x01, 0};
  desc1->setValue((uint8_t*)desc1_val, 1);
  reportChar1->addDescriptor(desc1);
  reportChar1->addDescriptor(new BLE2902());

  reportChar2 = pService1->createCharacteristic(
                                       (uint16_t)0x2a4d,
                                       BLECharacteristic::PROPERTY_READ   |
                                       BLECharacteristic::PROPERTY_WRITE                                       
                                     );
  BLEDescriptor *desc2 = new BLEDescriptor(BLEUUID((uint16_t)0x2908));
  const uint8_t desc2_val[] = {0x02, 0};
  desc2->setValue((uint8_t*)desc2_val, 1);
  reportChar2->addDescriptor(desc2);

  reportChar3 = pService1->createCharacteristic(
                                       (uint16_t)0x2a4d,
                                       BLECharacteristic::PROPERTY_READ     |
                                       BLECharacteristic::PROPERTY_WRITE    |
                                       BLECharacteristic::PROPERTY_WRITE_NR 
                                     );
  BLEDescriptor *desc3 = new BLEDescriptor(BLEUUID((uint16_t)0x2908));
  const uint8_t desc3_val[] = {0x03, 0};
  desc3->setValue((uint8_t*)desc3_val, 1);
  reportChar3->addDescriptor(desc3);

  BLECharacteristic *protocolModeChar = pService1->createCharacteristic(
                                       (uint16_t)0x2a4e,
                                       BLECharacteristic::PROPERTY_WRITE_NR 
                                     );

  BLECharacteristic *bootInputChar = pService1->createCharacteristic(
                                       (uint16_t)0x2a22,
                                       BLECharacteristic::PROPERTY_NOTIFY
                                     );
  bootInputChar->addDescriptor(new BLE2902());


  BLECharacteristic *bootOutputChar = pService1->createCharacteristic(
                                       (uint16_t)0x2a32,
                                       BLECharacteristic::PROPERTY_READ     |
                                       BLECharacteristic::PROPERTY_WRITE    |
                                       BLECharacteristic::PROPERTY_WRITE_NR 
                                     );



}

@chegewara Great work!!! It works like a charm on my Android phone. I can also connect the ESP32 with iOS and Mac OS, but no characters appear there - guess this is the security issue problem you were talking about. For testing I used two ESP32 Hellcat LoRa boards. One board is sending a string over LoRa, to the other board which is connected as HID device on Android transferring all input received as keystrokes.

Sorry but my windows has Polish localization :(

hid

Any news about this?
Would be super cool to use it as a AT/PS2 to BLE HID Keyboad adapter.

@ripper121 What news do you need? Its working for few weeks now. There is one example added. If you have questions, issues or suggestions im open and ready to help.

Is there a Arduino Lib for this or is it only possible with the SDK?

It should work with arduino too. Last time i checked it did work with arduino.

Yesss thx its working :)

Hi great work, I tried it on my windows 10, but couldn't manage to make it work.
First I got a "This device cannot start (Code 10)" and fixed it with https://www.kapilarya.com/fix-this-device-cannot-start-code-10-in-windows-10

After that I've got: "Driver error" - It connects and pairs ok, but doesn't work.

Monitor log:
Starting BLE work!
Characteristic defined! Now you can read it in your phone!
E (27800) BT: lmp_version_below LMP version 6 < 8
E (27860) BT: Call back not found for application conn_id=3
E (33978) BT: bta_gattc_conn_cback() - cif=3 connected=0 conn_id=3 reason=0x0013

any hint?
thanks

Hi,
most likely, and im only guessing right now, its problem with report map. Its this part of code (example code):

        const uint8_t reportMap[] = {
            USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
            USAGE(1),           0x06,       // Keyboard
            COLLECTION(1),      0x01,       // Application
            REPORT_ID(1),       0x01,       // REPORTID
            USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
            USAGE_MINIMUM(1),   0xE0,
            USAGE_MAXIMUM(1),   0xE7,
            LOGICAL_MINIMUM(1), 0x00,
            LOGICAL_MAXIMUM(1), 0x01,
            REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
            REPORT_COUNT(1),    0x08,
            INPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
            REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
            REPORT_SIZE(1),     0x08,
            INPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
            REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
            REPORT_SIZE(1),     0x01,
            USAGE_PAGE(1),      0x08,       //   LEDs
            USAGE_MINIMUM(1),   0x01,       //   Num Lock
            USAGE_MAXIMUM(1),   0x05,       //   Kana
            OUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
            REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
            REPORT_SIZE(1),     0x03,
            OUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
            REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
            REPORT_SIZE(1),     0x08,
            LOGICAL_MINIMUM(1), 0x00,
            LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
            USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
            USAGE_MINIMUM(1),   0x00,
            USAGE_MAXIMUM(1),   0x65,
            INPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
            END_COLLECTION(0)
        };

btw, ive been testing hid examples with windows 10 and there is no need to edit registers to make it works

If you can post report map i will try to check it, also screenshot with error would be great

@hmatulis To make sure that nothing has been changed in esp-idf that could have broke our code i just pulled latest esp-idf and cpp_utils, compiled HID keyboard sample and flashed it. All works fine on android phone and on my windows 10 laptop.

PS One thing, there is small change in esp-idf structure, which was forced by me on devs and they finally changed code, but its not related to your primary issue. This can cause compilation errors, but i have already fixed it and posted PR.

@chegewara I think I have the same problem, literally copy pasted your code, put it on an ESP32 geekworm devboard. Windows and Android recognize it as HID device, but it doesn't 'type' anything...

image

image

image
image

image

image

Could you guys test this bin file? To use it you need to remove .txt from name.

keyboard_hid.bin.txt

From first scrennshoot i can guess its something with this report map const uint8_t reportMap[].
Could you use this website to parse report map and send result?
http://eleccelerator.com/usbdescreqparser/

@lemio @hmatulis I didnt think about it earlier, there is bug in cpp_utils and we didnt know about it earlier, but we have had a workaround that help us to use ble.
If you are working with esp-idf and not arduino-ide then you can try to change this option in menuconfig:
https://github.com/nkolban/esp32-snippets/issues/264#issuecomment-357353186

@chegewara The binairy works! It prints Hello world from esp32 hid keyboard!!!

If I put your code into the USB Descriptor tool I get (But I don't really see why you are asking that):
`
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maximum (Kana)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x00, // Usage Minimum (0x00)
0x29, 0x65, // Usage Maximum (0x65)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection

// 63 bytes
`

Well, i dont wanted you to put my code but yours. Like i said, there may be some issue with sdkconfig.
You can compare your sdkconfig with this one, but this one is old and there is missing many new options introduced since then:
https://gist.github.com/chegewara/e33194b5276c346fd0c310fa99487feb

Had the same problem. The CONFIG_BTC_TASK_STACK_SIZE on my ESP32 was 8192 (not 3072). Changed it and now its working.

@gitmh
Thank you for referencing my example :-).
Although, this is mostly based on an example by Espressif guys.
I had quite a few problems with their example, especially the connecting process.

So I'm trying to build my app around @nkolban 's cpp_utils and all the BLE stuff here

Works much better on connecting, GAP handling and so on.
But: HID does not work with following error
Each time my phone connects (after pairing) & the keyboard task starts to send a report, the ESP runs into following assertion & panics:

assertion "getService() != nullptr" failed: function: void BLECharacteristic::notify()

As far as I understand (I'm not a CPP pro), the notify method uses its originally defined service.
If I use gdb to step through the backtrace, everything seems fine:

hid->m_hidService->m_pServer->m_connectedCount/m_gatts_if/m_connId

are all defined and have their corresponding values. (its the same for battery&dev info service)
AFAIK, the characteristic should use exactly this service.
So I don't understand why I have this error, if the service is available :-)?

I tried:

  • Debug/Release compiler options (suggested by @chegewara )
  • Original example with
hid->inputReport(NULL)->setValue(a,sizeof(a));
hid->inputReport(NULL)->notify();

and creating one characteristic for report & notify:

BLECharacteristic* test = hid->inputReport(NULL);
test->setValue(a,sizeof(a));
test->notify();
  • app_main compiled with gcc and g++
  • *

Sometimes it is a different exception of type "LoadStoreAlignment" in BLEService::getServer(),
but it is always around notify() and the server/service reference...

Do you have any suggestions?

Full log for assertion "getService() != nullptr" https://pastebin.com/kPvheTLa

Thank you very much for any hints, I appreciate! @nkolban Thank you very much for your work!

Hi @benjaminaigner ,
sorry for that, but this example is oudated. I did few small changes in hid class but forgot to change example.

Please try to follow this example to create input report and to pass arguments into hidInfo and pnp characteristics:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp

@chegewara
I noticed that by the compiler errors :-)
I've adapted my example ( https://github.com/asterics/esp32_mouse_keyboard/tree/newBLE ),
unfortunately I cannot test it until Monday.
Should I report here if I find an error? Or should I file a new issue?

@benjaminaigner Hi, this is not nice how you doing this. You took my example hid file and added your disclaimer in it.

https://github.com/asterics/esp32_mouse_keyboard/blob/newBLE/components/nkolban_BLE/HID_kbdmousejoystick.cpp

@chegewara
You are absolutely right, changed license header, added your name & removed mine.
At least I extended the HID report map :-), when I tested it, you can use it without credit notice (as an apology for my mistake).

@benjaminaigner Yes, you did. Just want to add that you can combine few report maps in one to have multiple device like keyboard + mouse etc in one esp32. If you have more issues you can post here or open new issue. Since this one is about hid its fine to post here.

EDIT i see you doing this already

@benjaminaigner If you would like you can ping me in issues on your github repo when you want some help. I dont mind to help you with this project without any interference in your code from my side, just providing expertise knowledge.

@chegewara And expertise you have (much of it, if I have a look at this repo). Thank you very much for the suggestion on my repo.

I wanted to build a simple HID keyboard based on an example, but I am somewhat confused now...

I have tried the old Arduino example mentioned here above, but it works only on Android. As I understand, that was the issue with it. So is there a working HID keyboard example for Arduino?

I have also checked out BLETests, but they seem to be written for an environment different than Arduino. Did everyone move to esp-idf? Even so, what do I do with those files? They do not seem 'makeable'...

This works for me with windows 10. There is minor issue, is not sending capital H, but i have no time to check it why. Its with txt extension because github does not allow to send ino file.
HID_keyboard.ino.txt

Fantastic! Yes, this works, I have finally something to tweak... Dzięki!

Anyone have a full working example with a joystick/gamepad? I have gotten the report map made and in windows it shows all the axes, buttons, etc. perfectly. However when I try to send a input report that is nothing happening in windows. Am I missing something for this to work? All of my keyboard and mouse examples work fine. Any guidance would be appreciated! Thanks.

Thanks for the link, That does help some. However I do believe my report is formatted properly as I can see everything under device manager in windows. It is just that when I add a value to the actual report to say press a button I get no activity. If it is any help I am under the arduino esp32 environment. I wonder if it would work better if I was under the IDF?

Im sure your report is formatted properly, other way windows would not install hid device and report driver error. Ive got sample gamepad running under arduino-ide, so its not the issue. Its all about setting proper values (bits for buttons) and send to proper inputReport to match reportID from reportMap.

@gitmh If you are still interested of running hid with iOS we have found "solution". Any peripheral that have to work with apple needs to fulfill some requirement. After all this time finally we have found the "issue". Its not implemented in this repo yet, but you can find recipe how to fix it here:
https://github.com/asterics/esp32_mouse_keyboard/issues/4#issuecomment-398674047

@chegewara I got it to work. Thanks for pointing me to the reportID. I had this in my report map. But it was a nested collection and I had the report I guess for the outer part not the inner. Everything works great now.

Thanks!

Was looking for a way to build a chorded keyboard and with a ESP32 dev board and some switches this works perfectly with my Android phone once paired.

My only issue - I have to unpair and pair every time the ESP32 is rebooted - any idea what I'm missing?

Thanks

Its hard to say. Do you have setup security, do you have setup bounding?

Erm .... no! I just took the example above and assumed :)

Sounds like I have some more reading to do, any suggested links or examples please?

Hi, I have a problem when I want to send two repeated characters ...

uint8_t c[] = {0x0, 0x0, 0x4, 0x4, 0x5, 0x5, 0x0, 0x0}; input->setValue(c, sizeof(c)); input->notify();

I get to cellphone ab and not aabb.
It work with ab1234 and not woth ab11234..
What is the problem?
Thank you very much

I will investigate it later, but in meantime could you test it with nRF connect and see the logs in it?

@juanfc After doing some tests i can say it has to be client implementation (usb or hid standard) and i can understand why. When you are typing on keyboard there is no possible to tap twice the same key fast enough to send it the way you are trying. For example: you cant tap two a because there is only one a on keyboard. I confirmed with nRF connect that even if i can programmaticaly send 6 same values and client will receive all 6, only one will be displayed.

Thanks for your answer!
I tried 100ms of delay and it did not work. Do you think there is another way?
uint8_t c[] = {0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0};
input->setValue(c, sizeof(c)); input->notify();
delay(150);
input->setValue(c, sizeof(c)); input->notify();
Thanks ;)

I see what you want to achieve. You need something like this:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp#L46-L56

After sending key down code, you need to send key up code (uint8_t c[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};) then you will be able to repeat the same key/character.

Thanks, I try when I can. Greeting

El mar., 10 jul. 2018 22:09, chegewara notifications@github.com escribió:

I see what you want to achieve. You need something like this:

https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp#L46-L56

After sending key down code, you need to send key up code (uint8_t c[] =
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};) then you will be able to repeat
the same key/character.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/nkolban/esp32-snippets/issues/230#issuecomment-404021945,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AFCzhD9U9Db82C_dfvsXq6TVcDLnqYVyks5uFV50gaJpZM4QqyGU
.

thanks chegewara! Work fine!
best regards

Hi!
An extra question: can I always send key up coede even if the character is not repeated?

Yes, i think you should send key up code, but it does not mean it has to be all 8 bytes 0x00 (but it can be all 8 bytes 0x00). When you send new key down value and there is no old key down repeated then client is assuming that old key up was issued.

Assuming you have normal keyboard from the shop, when you press key down some value is sent, then when you press new key down and old key is up, then only new value is send. But when you dont push new key down but you leave old key up then bytes 2-7 are sent 0x0 and byte 0 has value with flags according to num and caps lock, shift etc.

Thanks for all the information, and your quick response! Excellent work!

Hi

A few of you have mentioned you have working code for using Arduino to create Bluetooth HID gamepads with ESP32

Would you mind pointing me in the right direction to finding this code?

Much appreciated

Hi @chegewara,

I tried almost all of the example codes I could find on the internet, sadly none of them works on Mac OS. It appears that the ESP device is successfully recognized and paired except there's always a loading icon shown on the right.

image

However after a few seconds, the ESP device just disappeared from the devices list, and nothing was output from the device whatever it is set to a HID_MOUSE or a HID_KEYBOARD.

Does anyone successfully use ESP32 as BLE HID device on Mac OS X? Help please.


* UPDATES *

Well, after a long time research, I finally figured out it was caused by the security rules of macOS / iOS.
In order to use a BLE HID on Apple's systems, you have to disable the security at first, and set the permissions of all the characteristics to both ESP_GATT_PERM_READ_ENCRYPTED and ESP_GATT_PERM_WRITE_ENCRYPTED.

auto m_pnpCharacteristic = hid->deviceInfo()->getCharacteristic(BLEUUID((uint16_t)0x2a50));
m_pnpCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
auto m_hidInfoCharacteristic = hid->hidService()->getCharacteristic(BLEUUID((uint16_t)0x2a4a));
m_hidInfoCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
auto m_reportMapCharacteristic = hid->hidService()->getCharacteristic(BLEUUID((uint16_t)0x2a4b));
m_reportMapCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
auto m_hidControlCharacteristic = hid->hidService()->getCharacteristic(BLEUUID((uint16_t)0x2a4c));
m_hidControlCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
auto m_protocolModeCharacteristic = hid->hidService()->getCharacteristic(BLEUUID((uint16_t)0x2a4e));
m_protocolModeCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

auto m_batteryLevelCharacteristic = hid->batteryService()->getCharacteristic(BLEUUID((uint16_t)0x2a19));
m_batteryLevelCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

auto m_manufacturerCharacteristic = hid->deviceInfo()->getCharacteristic(BLEUUID((uint16_t)0x2a29));
m_manufacturerCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

input->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
output->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

BLESecurity *pSecurity = new BLESecurity();
pSecurity->setCapability(ESP_IO_CAP_NONE);

Yes, sorry for that. It was fixed in asterics library, but i didnt add it to this library yet. Im glad you found it.

Hi. First, thank you all for your work and your talks, it helped me a lot !
I try to make my ESP32 work as a BLE HID on all OS. I have made it work on Linux, Windows, MacOS and Android but still can't make it work on iOS despite all chegewara advice.
I have no trouble to connect to the ESP32 and it is recognize as a HID device (on the Note app, the keyboard disappear) but no characters are written.
The code I use is the following:

#include <rom/rtc.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"


#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID

static BLEAddress *pServerAddress;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLERemoteCharacteristic* pRemoteCharacteristic2;

BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

bool connected = false;
std::string test;
const char* hello = "";

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

class MyCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer){
    connected = true;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
    desc->setNotifications(true);
  }

  void onDisconnect(BLEServer* pServer){
    connected = false;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
    desc->setNotifications(false);
  }
};

 class MyOutputCallbacks : public BLECharacteristicCallbacks {
 void onWrite(BLECharacteristic* me){
    uint8_t* value = (uint8_t*)(me->getValue().c_str());
    ESP_LOGI(LOG_TAG, "special keys: %d", *value);
  }
};

void taskServer(void*){
  BLEDevice::init("test");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyCallbacks());
  hid = new BLEHIDDevice(pServer);
  input = hid->inputReport(1); // <-- input REPORTID from report map
  output = hid->outputReport(1); // <-- output REPORTID from report map
  output->setCallbacks(new MyOutputCallbacks());
  std::string name = "test";
  hid->manufacturer()->setValue(name);
  hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
  hid->hidInfo(0x00,0x02);
  BLEService *pService = pServer->createService(SERVICE_UUID);
  pService->start();
  BLESecurity *pSecurity = new BLESecurity();;
  pSecurity->setCapability(ESP_IO_CAP_NONE);

  const uint8_t report[] = {
    USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
    USAGE(1),           0x06,       // Keyboard
    COLLECTION(1),      0x01,       // Application
    REPORT_ID(1),       0x01,        //   Report ID (1)
    USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
    USAGE_MINIMUM(1),   0xE0,
    USAGE_MAXIMUM(1),   0xE7,
    LOGICAL_MINIMUM(1), 0x00,
    LOGICAL_MAXIMUM(1), 0x01,
    REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
    REPORT_COUNT(1),    0x08,
    HIDINPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
    REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
    REPORT_SIZE(1),     0x08,
    HIDINPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
    REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
    REPORT_SIZE(1),     0x08,
    LOGICAL_MINIMUM(1), 0x00,
    LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
    USAGE_MINIMUM(1),   0x00,
    USAGE_MAXIMUM(1),   0x65,
    HIDINPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
    REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
    REPORT_SIZE(1),     0x01,
    USAGE_PAGE(1),      0x08,       //   LEDs
    USAGE_MINIMUM(1),   0x01,       //   Num Lock
    USAGE_MAXIMUM(1),   0x05,       //   Kana
    HIDOUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
    REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
    REPORT_SIZE(1),     0x03,
    HIDOUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
    END_COLLECTION(0)
  };
  hid->reportMap((uint8_t*)report, sizeof(report));
  hid->startServices();
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->setAppearance(HID_KEYBOARD);
  pAdvertising->addServiceUUID(hid->hidService()->getUUID());
  pAdvertising->start();
  hid->setBatteryLevel(7);
  ESP_LOGD(LOG_TAG, "Advertising started!");
  delay(portMAX_DELAY);
}

void loop() {
  if(connected) {
    hello = "abcd";
    delay(5000);
    while(*hello){
      KEYMAP map = keymap[(uint8_t)*hello];
      uint8_t msg[] = {map.modifier, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0};
          Serial.print(map.modifier);
          Serial.print(" ");
          Serial.println(map.usage);
      input->setValue(msg,sizeof(msg));
      input->notify();
      hello++;
      uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
      input->setValue(msg1,sizeof(msg1));
      input->notify();
      delay(50);
    }
  }
}

I have changed BLEHIDDevice.cpp to add all the setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED) and I also changed BLEDescriptor.cpp to change the hard-coded permissions setting to (esp_gatt_perm_t)(m_permissions)

Here are the log I get : https://pastebin.com/c21SqcfG
I would be very glad if anyone as a solution :)
Thank you !

Try this:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/ArduinoBLE.md#replacing-the-version-that-comes-with-arduino-esp32

EDIT you dont need this in your code:

BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);   <--- THIS
    desc->setNotifications(true);
  }

  void onDisconnect(BLEServer* pServer){
    connected = false;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);  <--- THIS
    desc->setNotifications(false);

No changes, still works on MacOS but not on iOS :(

You have to wait till i borrow iPhone, but even then no promise i can find issue.

BTW if anyone got spare iPhone that can send i dont mind

No changes, still works on MacOS but not on iOS :(

Hi,

I tried your short example on macOs Mojave and it connects, but there are no keypresses recognised from the OS (e.g. in the Notes app). Should this happen (or maybe is my ESP32 faulty)?

I'm on macOS High Sierra and it's working great. Do you have the up to date library ?

I am fighting with iPhone now, no key presses recognized too. Im getting crazy.

I'm on macOS High Sierra and it's working great. Do you have the up to date library ?

I hope so ;)
(downloaded from https://github.com/chegewara/esp32-snippets/tree/multi_connect/cpp_utils with the latest commit https://github.com/chegewara/esp32-snippets/commit/bac1b24dee019a14949509b67155be1797b59757)

I will try to check again, if I made a mistake.

@chegewara have you sorted out your iPhone situation? I have an older iphone (5? 5s?..) that I could send your way – for science. Let me know.

I have no iPhone to test, but there is one small chance i did. Try this:
BLEHIDDevice* hid; <--- delete static operand in main app code

If you are willing to send my iPhone i dont mind. 🥇

@chegewara where could we discuss the shipping arrangement in a more private way? I do have an older iphone that I’m sure you’ll find a use for.

I have no iPhone to test, but there is one small chance i did. Try this:
BLEHIDDevice* hid; <--- delete static operand in main app code

The BLEHIDDevice is not declared as static in my code.

Still have no keystrokes on MacOS. Tried @Azalgo's code from above with multi_connect branch from Che – no avail. Symptoms with that code are as follows: I am able to connect, but then device is never actually connected to: it is left for a couple of minutes in the list with a spinny wheel against it and then disconnected.

What I've tried is to use this: https://github.com/asterics/esp32_mouse_keyboard and it's sorta kinda gets me a step further: I am able to stay "connected" and there is no more spinny wheel in settings, but still no keystrokes.

What am I missing?..

You are missing nothing, its something i am missing. For example i know there is floating battery characteristic without assigned value. Most likely its too simple but it can be this.
I am running wireshark and as for now its only malformed packet that is exchanged between esp32 and windows 10. Only windows 10 laptop can accept this and stay connected and keystrokes are received.

With asterics library i am able to connect and send keystroke from putty by pressing 'q', it will send y key.

I do have a hid->setBatteryLevel(7); in my code. Guess what it does to the overall situation? Nothing.

I think it can be something wrong with sampleexample itself, not with library, because last my test with iPhone 2 days ago was as follow:

  • get asterics repo,
  • instead his ble library use latest ble library from this repo (or currently mine),
  • try it, and at the end it still works.

Sorry, but it is very frustrating and i have to rest from hid. I will try to add some more functionalities to library now. Have a good luck.

In my experiments, I could not send keystrokes to a MacOS 10.14 with this library's example code, asterics's library example code (testmode = 1) or my own contraptions. Maximum of what I've got is for it to show as connected in "devices" preferences panel. I must note that my code works alright with android (maybe with windows too? Don't have a machine to test atm).

Asterics's lib even comes with sdkconfig and partition table (and I've used that), so that is not the cause of the problem.

If anyone has a project that they know can send keystrokes to iOS/MacOS (or both!) please share. I want to know what is different and fix it in this lib so it works everywhere equally great.

@toxuin this is most frustrating thing with this library. You are saying that asterics code does not work for you, and i just few days ago got iPhone for few hours and it did work for me. All i had to do is git clone, and make flash, but you have to use newBLE branch, not master.

@chegewara Thank you for your excellent example I could finally made my first BLE HID project.

I think I should also share the source code, it is an ESP32 based BLE Keynote/PPT remote controller.

I also made a BLE Midi Controller and a USB HID Device.

@MagicCube Im glad you found this library useful and very nice of you that you are sharing with us what you did.

BTW i am thinkg about changing BLEHID class to make it even easier to use, but now i am busy with other upgrades

I think you can share with wider community posting your code on forum in Showcase

Hello All,

I was experimenting on esp32 board to create an HID mouse with Arduino library using code snippets provided in this thread, and modifying the keyboard example. So far I am able to pair and see a BLE mouse appear on my smartphone scan. How do I send the actual coordinate values in the message?
Any help is appreciated as I am new to this.

Here is my arduino code

===============================================================================

include

include

include

include "BLE2902.h"

include "BLEHIDDevice.h"

include "HIDTypes.h"

include "HIDKeyboardTypes.h"

include

BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

uint8_t buttons = 0;
uint8_t button1 = 0;
uint8_t button2 = 0;
uint8_t button3 = 0;
bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer){
connected = true;
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setNotifications(true);
}

void onDisconnect(BLEServer* pServer){
connected = false;
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setNotifications(false);
}
};

/*

  • This callback is connect with output report. In keyboard output report report special keys changes, like CAPSLOCK, NUMLOCK
  • We can add digital pins with LED to show status
  • bit 0 - NUM LOCK
  • bit 1 - CAPS LOCK
  • bit 2 - SCROLL LOCK
    /
    class MyOutputCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic
    me){
    uint8_t* value = (uint8_t*)(me->getValue().c_str());
    ESP_LOGI(LOG_TAG, "special keys: %d", *value);
    }
    };

void taskServer(void*){

BLEDevice::init("ESP32-mouse");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyCallbacks());

hid = new BLEHIDDevice(pServer);
input = hid->inputReport(1); // <-- input REPORTID from report map
output = hid->outputReport(1); // <-- output REPORTID from report map

output->setCallbacks(new MyOutputCallbacks());

std::string name = "chegawara";
hid->manufacturer()->setValue(name);

hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
hid->hidInfo(0x00,0x02);

BLESecurity *pSecurity = new BLESecurity();
// pSecurity->setKeySize();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);

/*
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x03, // REPORT_COUNT (3)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
*/

const uint8_t report[] = {
USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls
USAGE(1), 0x02,
COLLECTION(1), 0x01, // Application
USAGE(1), 0x01,
COLLECTION(1), 0x00,
USAGE_PAGE(1), 0x09,
USAGE_MINIMUM(1), 0x01,
USAGE_MAXIMUM(1), 0x03,
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0x01,
REPORT_COUNT(1), 0x03,
REPORT_SIZE(1), 0x01,
HIDINPUT(1) , 0x02,
REPORT_COUNT(1), 0x01,
REPORT_SIZE(1), 0x05,
HIDINPUT(1) , 0x03,
USAGE_PAGE(1), 0x01,
USAGE(1), 0x30,
USAGE(1), 0x31,
USAGE_MINIMUM(1), 0x81,
USAGE_MAXIMUM(1), 0x7f,
REPORT_SIZE(1), 0x08,
REPORT_COUNT(1), 0x02,
HIDINPUT(1), 0x06,
END_COLLECTION(0)
};

hid->reportMap((uint8_t*)report, sizeof(report));
hid->startServices();

BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->setAppearance(HID_MOUSE);
pAdvertising->addServiceUUID(hid->hidService()->getUUID());
pAdvertising->start();
hid->setBatteryLevel(7);

ESP_LOGD(LOG_TAG, "Advertising started!");
delay(portMAX_DELAY);

};

void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");

xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

void loop() {

if(connected){

}

}

@chegewara I have tried these examples and also https://github.com/nkolban/esp32-snippets/issues/621
when I connect the mouse to an android phone. I sometimes see a message saying pairing unsuccessful as passcode didn't match or if the pairing and connection is successful I don't see any pointer appear on the screen and don't see any movement.
Any ideas?

@renu285 It was just reference link where you can find hid mouse implemented in code based on this library. You can find info here:
http://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/

To build hid device you need to understand basics of report map or find library where you have already build in such report maps. Hid mouse is not complicated though. What you need is to send 1 byte that will have info about buttons state (usually 3 buttons, but you can have more) and 2 bytes with info about X and Y mouse movement. If you dont see cursor then probably you have something wrong with report map. You can find report map here:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLETests/SampleHIDDevice.cpp#L112-L142

I dont have time now, but i have plans to refactor HID class to make it even easier to use with 3 basic usage report maps (keyboard, mouse, gamepad/joystick).

Hello,

After several days of digging around and trying to understand how input reports work I finally was able to get it to work (sort of). I can now move around the mouse pointer (both x and y) and also the scroll wheel works. (Mandatory needed by Windows10)

However I am unable to get the mouse to click anything i.e perform left click, right click etc.

Here is the input report I am using that I scavenged off of internet . It has 5 buttons defined.
My current reporting format that works for me are as follows a[] = {x,y,vertical-wheel,hori-wheel,0}
It doesn't generate any clicks even after passing any values to a[4]. Don't know what to pass to get it to click with the following format. I have tried other reports but this is the only one that works so far

const uint8_t report[] = {

 0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
 0x09, 0x02,        // USAGE (Mouse)
 0xa1, 0x01,        // COLLECTION (Application)
 0x09, 0x02,        //   USAGE (Mouse)
 0xa1, 0x02,        //   COLLECTION (Logical)
 0x09, 0x01,        //     USAGE (Pointer)
 0xa1, 0x00,        //     COLLECTION (Physical)
                    // ------------------------------  Buttons
 0x05, 0x09,        //       USAGE_PAGE (Button)      
 0x19, 0x01,        //       USAGE_MINIMUM (Button 1)
 0x29, 0x05,        //       USAGE_MAXIMUM (Button 5)
 0x15, 0x00,        //       LOGICAL_MINIMUM (0)
 0x25, 0x01,        //       LOGICAL_MAXIMUM (1)
 0x75, 0x01,        //       REPORT_SIZE (1)
 0x95, 0x05,        //       REPORT_COUNT (5)
 0x81, 0x02,        //       INPUT (Data,Var,Abs)
                    // ------------------------------  Padding
 0x75, 0x03,        //       REPORT_SIZE (3)
 0x95, 0x01,        //       REPORT_COUNT (1)
 0x81, 0x03,        //       INPUT (Cnst,Var,Abs)
                    // ------------------------------  X,Y position
 0x05, 0x01,        //       USAGE_PAGE (Generic Desktop)
 0x09, 0x30,        //       USAGE (X)
 0x09, 0x31,        //       USAGE (Y)
 0x15, 0x81,        //       LOGICAL_MINIMUM (-127)
 0x25, 0x7f,        //       LOGICAL_MAXIMUM (127)
 0x75, 0x08,        //       REPORT_SIZE (8)
 0x95, 0x02,        //       REPORT_COUNT (2)
 0x81, 0x06,        //       INPUT (Data,Var,Rel)
 0xa1, 0x02,        //       COLLECTION (Logical)
                    // ------------------------------  Vertical wheel res multiplier
 0x09, 0x48,        //         USAGE (Resolution Multiplier)
 0x15, 0x00,        //         LOGICAL_MINIMUM (0)
 0x25, 0x01,        //         LOGICAL_MAXIMUM (1)
 0x35, 0x01,        //         PHYSICAL_MINIMUM (1)
 0x45, 0x04,        //         PHYSICAL_MAXIMUM (4)
 0x75, 0x02,        //         REPORT_SIZE (2)
 0x95, 0x01,        //         REPORT_COUNT (1)
 0xa4,              //         PUSH
 0xb1, 0x02,        //         FEATURE (Data,Var,Abs)
                    // ------------------------------  Vertical wheel
 0x09, 0x38,        //         USAGE (Wheel)
 0x15, 0x81,        //         LOGICAL_MINIMUM (-127)
 0x25, 0x7f,        //         LOGICAL_MAXIMUM (127)
 0x35, 0x00,        //         PHYSICAL_MINIMUM (0)        - reset physical
 0x45, 0x00,        //         PHYSICAL_MAXIMUM (0)
 0x75, 0x08,        //         REPORT_SIZE (8)
 0x81, 0x06,        //         INPUT (Data,Var,Rel)
 0xc0,              //       END_COLLECTION
 0xa1, 0x02,        //       COLLECTION (Logical)
                    // ------------------------------  Horizontal wheel res multiplier
 0x09, 0x48,        //         USAGE (Resolution Multiplier)
 0xb4,              //         POP
 0xb1, 0x02,        //         FEATURE (Data,Var,Abs)
                    // ------------------------------  Padding for Feature report
 0x35, 0x00,        //         PHYSICAL_MINIMUM (0)        - reset physical
 0x45, 0x00,        //         PHYSICAL_MAXIMUM (0)
 0x75, 0x04,        //         REPORT_SIZE (4)
 0xb1, 0x03,        //         FEATURE (Cnst,Var,Abs)
                    // ------------------------------  Horizontal wheel
 0x05, 0x0c,        //         USAGE_PAGE (Consumer Devices)
 0x0a, 0x38, 0x02,  //         USAGE (AC Pan)
 0x15, 0x81,        //         LOGICAL_MINIMUM (-127)
 0x25, 0x7f,        //         LOGICAL_MAXIMUM (127)
 0x75, 0x08,        //         REPORT_SIZE (8)
 0x81, 0x06,        //         INPUT (Data,Var,Rel)
 0xc0,              //       END_COLLECTION
 0xc0,              //     END_COLLECTION
 0xc0,              //   END_COLLECTION
 0xc0               // END_COLLECTION

};

@renu285 If i may suggest something, this report map is way too complicated for mouse. Im not saying it may or may not work, just saying there is a lot functionalities that you for sure not implement in your device.

This report map is sufficient:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLETests/SampleHIDDevice.cpp#L116-L141
only thing that can be missing here is reportID. When ive been working on this class and everything that is related to HID i found that windows requires or works better when report map has reportID:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp#L124
If this report map wont work with windows then probably is missing wheel (like you mention its most likely mandatory).

Now back to your report map. Buttons should be in byte 0 since it is first element in report map (like you said 5 buttons, 1 bit for each button and bits padding). All bytes are counted from beginning of report map. Next you should have cursor X and Y movement, one byte for each. Then wheels, but here i am not sure because i did not study push/pop and it was about 10 months ago when ive been working with HID.

If i can help you somehow else just ask.

Thank you very much for your reply @chegewara. I have been struggling with these report maps from quite some time now. I am aware that for my purpose a simple x,y,button combination is fine and I have already tried the report maps in the links that you mentioned, But none of them seem to be recognized by android and windows even with the wheel. The report map which I have shared is the only one which works except the button clicks.

In the reportmap I have shared, from what I understand buttons should be in byte zero as it is at the beginning of the report map but in practice this is not the case I get x,y movements when I send something in byte 0 and byte 1, I get scroll movements when I send something in byte 2 and first 4 bits of byte 3 and no matter what I send nowhere I get right/left/click etc. I even modified the report map to have only 3 buttons and 5 padded bits but there is no improvement.

Ive been testing this report map and it for sure works with android (windows i dont remember).

I will try to run simple hid example to confirm when i find time, probably even this week.

Sure that will be very helpful...If you can share the working code

https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/
@renu285

struct mouse_report_t
{
uint8_t buttons;
int8_t x;
int8_t y;
}

I want to make a DIY footswitch. I have some questions... Firstly, I'm searching for a bluetooth module. I have come across BC417, CC2540, CC2640, ESP32...
I found the CC2540 is cheaper, just $1.45 https://www.aliexpress.com/item/AT-09-Android-IOS-BLE-4-0-Bluetooth-module-for-arduino-CC2540-CC2541-Serial-Wireless-Module/32818178064.html
The ESP32 is not that cheap, is $2.95 https://www.aliexpress.com/item/ESP-32S-ESP-WROOM-32-ESP32-ESP-32-Bluetooth-and-WIFI-Dual-Core-CPU-with-Low/32802438946.html

But it seems there are more people working on the ESP32 compared to CC2540...? Are there anyother modules that can be used for a keyboard? And how to choose, which one is the best?

I also want the keyboard to have a function like the logitech's easy switch https://www.logitech.com/en-us/product/illuminated-keyboard-for-mac-ipad-iphone (using the same keyboard to pair with different computers and switch among them with one click). Can these modules do this?

Also, is it possible to change the footswitch's key easily? Using OTA update?

Hi @coreboot2
did you try app from OP? Code is based on this library and as far as i know its working with iPhone and macOS. This library may be, im not saying it is, less stable now in HID apps at the moment. Its caused by changes i made in it to implement more features and still require a lot tests and some changes.

Hi @chegewara I had a try of the HID_keyboard.ino arduino code you posted above, but I can't get it work. After it's compiled and uploaded. Everytime it boots it shows like this:

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:2 load:0x3fff0018,len:4 load:0x3fff001c,len:1100 load:0x40078000,len:9220 load:0x40080400,len:6312 entry 0x400806a4 Starting BLE work! ⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮

Then it's continuing the reverse question mark.


Update: It's working now... It's because of a poor contact...

Since I just want a one button footswitch, but I haven't learned how to write that code to detect the IO pin...
So I just modified one of the key code in @MagicCube 's keynote controller's code. But I get this error https://github.com/MagicCube/esp32-keynotes-controller/issues/2 , how to fix this?


Update: Get it working now! After run a "python esptool.py erase_flash", now everything is working!!

Hi @chegewara, I have downloaded the ESP_BLE HID Keyboard code and have it working in the Arduino environment. I can send upper and lower case keys, but I am trying to figure out how to send special keys for media transport like fast forward, rewind, home. Does the BLE library support those keys? Do you have a sample piece of code that shows how to send them?

Thanks,

Paul

Here is the part of my code that has to do with the BLE HID, the function send_BT() sends the keycode in conBuf[7] and the modifier in conBuf[8] shift state. As I mentioned before I would like to know how to send special codes like fast forward/play/rewind.

Thanks,

Paul

BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer){
connected = true;
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setNotifications(true);
}

void onDisconnect(BLEServer* pServer){
connected = false;
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setNotifications(false);
}
};

/*

  • This callback is connect with output report. In keyboard output report report special keys changes, like CAPSLOCK, NUMLOCK
  • We can add digital pins with LED to show status
  • bit 0 - NUM LOCK
  • bit 1 - CAPS LOCK
  • bit 2 - SCROLL LOCK
    /
    class MyOutputCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic
    me){
    uint8_t* value = (uint8_t*)(me->getValue().c_str());
    ESP_LOGI(LOG_TAG, "special keys: %d", *value);
    }
    };

void taskServer(void*){
BLEDevice::init("FST");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyCallbacks());

hid = new BLEHIDDevice(pServer);
input = hid->inputReport(1); // <-- input REPORTID from report map
output = hid->outputReport(1); // <-- output REPORTID from report map

output->setCallbacks(new MyOutputCallbacks());

std::string name = "ESP32";
hid->manufacturer()->setValue(name);

hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
hid->hidInfo(0x00,0x02);

BLESecurity *pSecurity = new BLESecurity();
// pSecurity->setKeySize();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);

const uint8_t report[] = {
  USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
  USAGE(1),           0x06,       // Keyboard
  COLLECTION(1),      0x01,       // Application
  REPORT_ID(1),       0x01,        //   Report ID (1)
  USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
  USAGE_MINIMUM(1),   0xE0,
  USAGE_MAXIMUM(1),   0xE7,
  LOGICAL_MINIMUM(1), 0x00,
  LOGICAL_MAXIMUM(1), 0x01,
  REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
  REPORT_COUNT(1),    0x08,
  HIDINPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
  REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
  REPORT_SIZE(1),     0x08,
  HIDINPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
  REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
  REPORT_SIZE(1),     0x08,
  LOGICAL_MINIMUM(1), 0x00,
  LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
  USAGE_MINIMUM(1),   0x00,
  USAGE_MAXIMUM(1),   0x65,
  HIDINPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
  REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
  REPORT_SIZE(1),     0x01,
  USAGE_PAGE(1),      0x08,       //   LEDs
  USAGE_MINIMUM(1),   0x01,       //   Num Lock
  USAGE_MAXIMUM(1),   0x05,       //   Kana
  HIDOUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
  REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
  REPORT_SIZE(1),     0x03,
  HIDOUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
  END_COLLECTION(0)
};

hid->reportMap((uint8_t*)report, sizeof(report));
hid->startServices();

BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->setAppearance(HID_KEYBOARD);
pAdvertising->addServiceUUID(hid->hidService()->getUUID());
pAdvertising->start();
hid->setBatteryLevel(7);

ESP_LOGD(LOG_TAG, "Advertising started!");

vTaskDelete(NULL);
delay(portMAX_DELAY);
};

void send_BT(){
if(connected){
Serial.println("Key Code=" + String(conBuf[7])+ " Mod=" + String(conBuf[8]));
char key_code = conBuf[7];
char key_mod = conBuf[8];
uint8_t msg[] = {key_mod,0x0,key_code,0x0,0x0,0x0,0x0,0x0};
input->setValue(msg,sizeof(msg));
input->notify();

  uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
  input->setValue(msg1,sizeof(msg1));
  input->notify();

}else{
Serial.println("BLE is not connected");
}
}

Best way to start is to use nRF connect. I am using android because i dont like apple. If you will see that esp32 and smartphone are paired/bond then you can start to look at characteristics when esp32 sends notifications. Then just give it a try and open notes on smartphone.

espressif/esp-idf#3051

I am assuming from the table you pointed to(report map), that I would do the following to send a FAST FORWARD, set key mod to 0x9 and set keycode to 0xb3? I tested it and it did not work for me.

uint8_t msg[] = {0x9,0x0,0xb3,0x0,0x0,0x0,0x0,0x0};
input->setValue(msg,sizeof(msg));
input->notify();

But i dont know what this has to do with HID. Im not saying i am HID expert, but as far as i know HID keyboard can only send key code value and host has to interpret it.
Of course HID protocol its not only keyboard, mouse or gamepad.
I would start with this:
https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf

@pwolfman What you need is to have Consumer device in report map.
https://www.microchip.com/forums/m618147.aspx

I did not read this document, but seems you can find useful info in it:
https://media.readthedocs.org/pdf/adafruit-circuitpython-hid/latest/adafruit-circuitpython-hid.pdf

EDIT in this document page 9
https://cdn.sparkfun.com/datasheets/Wireless/Bluetooth/RN-HID-User-Guide-v1.0r.pdf

Hi all!
First of all thanks, Neil, to this wonderful piece of code!
I wanted to use it to send keyboard data to android as well as ios, but it just didn't work on the latter, while connetcions were made without any issues.
Last night I made one change and I'm there - and wanted to let you know what it was, just in case others are at the same point.
It seemed to be the report description. I just pasted in the one from another bt-kbd project and it worked out:
https://github.com/lkundrak/btkbdd/blob/master/btkbdd/kbd.h

All the best!

This works for me with windows 10. There is minor issue, is not sending capital H, but i have no time to check it why. Its with txt extension because github does not allow to send ino file.
HID_keyboard.ino.txt

hi @chegewara HID_keyboard.ino.txt is working for windows 10, but BLE_Mouse.ino is not working in windows10 but it works in android. please help
BLE_Mouse.ino.txt

First of all; very cool and active thread! Can somebody please make some sort of summary of the samples posted in this thread? I am using this thread for inspiration to see if i can make a bluetooth keyboard in NodeJS.

I am planning to use this to control/automate my nVidia Shield TV (Android box) using BLE. Source of commands would be my universal remote which can sends commands to custom 'drivers' which run in your network. Most of these are implemented using NodeJS. servers. When receiving button presses i would like to use Bleno (nodejs library) to relay these commands to my Shield TV.

If that implementation fails, i want to expand the samples provided here to support MQTT connection and flash that code to an ESP32. That would be my fallback scenario. Off course i will give credits and share my code once completed, for learning reference, but for now i've never done anything with Bluetooth on the ESP so i need some guidance :)

Things i've taken out from this post:
1) Reportmap can be small, needs reportid.
2) There is a sample here; but it hasn't been updated since june 2008
3) With googling i think i've found @chegewara 's initial code here; https://www.esp32.com/viewtopic.php?t=4122

This is working with windows 10 BLEMouse code:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"
#include <driver/adc.h>
#include "sdkconfig.h"


BLEHIDDevice* hid;
BLECharacteristic* inputMouse;


bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer){
    connected = true;
    BLE2902* desc = (BLE2902*)inputMouse->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(true);
  }

  void onDisconnect(BLEServer* pServer){
    connected = false;
    BLE2902* desc = (BLE2902*)inputMouse->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(false);
  }
};



void taskServer(void*){


    BLEDevice::init("Flip-O-Matic");
    BLEServer *pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyCallbacks());

    hid = new BLEHIDDevice(pServer);
    inputMouse = hid->inputReport(1); // <-- input REPORTID from report map

    std::string name = "chegewara";
    hid->manufacturer()->setValue(name);

    hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
    hid->hidInfo(0x00,0x02);

  BLESecurity *pSecurity = new BLESecurity();

  pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);

// http://www.keil.com/forum/15671/usb-mouse-with-scroll-wheel/
// Wheel Mouse - simplified version - 5 button, vertical and horizontal wheel
//
// Input report - 5 bytes
//
//     Byte | D7      D6      D5      D4      D3      D2      D1      D0
//    ------+---------------------------------------------------------------------
//      0   |  0       0       0    Forward  Back    Middle  Right   Left (Buttons)
//      1   |                             X
//      2   |                             Y
//      3   |                       Vertical Wheel
//      4   |                    Horizontal (Tilt) Wheel
//
// Feature report - 1 byte
//
//     Byte | D7      D6      D5      D4   |  D3      D2  |   D1      D0
//    ------+------------------------------+--------------+----------------
//      0   |  0       0       0       0   |  Horizontal  |    Vertical
//                                             (Resolution multiplier)
//
// Reference
//    Wheel.docx in "Enhanced Wheel Support in Windows Vista" on MS WHDC
//    http://www.microsoft.com/whdc/device/input/wheel.mspx
//


const uint8_t reportMapMouse[] = {

USAGE_PAGE(1),       0x01,
      USAGE(1),         0x02,
       COLLECTION(1),     0x01,
       REPORT_ID(1),  0x01,
       USAGE(1),        0x01,
       COLLECTION(1),     0x00,
       USAGE_PAGE(1),     0x09,
       USAGE_MINIMUM(1),    0x1,
       USAGE_MAXIMUM(1),    0x3,
       LOGICAL_MINIMUM(1),  0x0,
       LOGICAL_MAXIMUM(1),  0x1,
       REPORT_COUNT(1),   0x3,
       REPORT_SIZE(1),    0x1,
       0x80|0x01,        0x2,    // (Data, Variable, Absolute), ;3 button bits
       REPORT_COUNT(1),   0x1,
       REPORT_SIZE(1),    0x5,
       0x80|0x01,        0x1,    //(Constant), ;5 bit padding
       USAGE_PAGE(1),     0x1,    //(Generic Desktop),
       USAGE(1),        0x30,
       USAGE(1),        0x31,
       LOGICAL_MINIMUM(1),  0x81,
       LOGICAL_MAXIMUM(1),  0x7f,
       REPORT_SIZE(1),    0x8,
       REPORT_COUNT(1),   0x2,
       0x80|0x01,        0x6,    //(Data, Variable, Relative), ;2 position bytes (X & Y)
       END_COLLECTION(0),
      END_COLLECTION(0)
};

    hid->reportMap((uint8_t*)reportMapMouse, sizeof(reportMapMouse));
    hid->startServices();

    BLEAdvertising *pAdvertising = pServer->getAdvertising();
    pAdvertising->setAppearance(HID_MOUSE);
    pAdvertising->addServiceUUID(hid->hidService()->getUUID());
    pAdvertising->start();
    hid->setBatteryLevel(7);

    ESP_LOGD(LOG_TAG, "Advertising started!");
    delay(portMAX_DELAY);

};


const int button1Pin = 39;     // the number of the pushbutton pin
const int button2Pin = 38;     // the number of the pushbutton pin
const int ledPin =     5;      // the number of the LED pin

void setup() {

  Serial.begin(115200);
  Serial.println("Starting BLE work!");


  pinMode(button1Pin, INPUT_PULLUP);
  pinMode(button2Pin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);

  xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

void loop() {

  if(connected){

    //vTaskDelay(5000);Serial.println("dormindo");

     while (digitalRead(button2Pin) ==  LOW ){ 

          Serial.println("mouse Scroll DOWN");
          //<button>, <x>, <y>, <wheel>

          uint8_t msg[] = {  0x00, 0x00, -0x01};

          inputMouse->setValue(msg,3);
          inputMouse->notify();
          delay(10);



     }

    while (digitalRead(button1Pin) ==  LOW ){ 

          Serial.println("mouse Scroll UP");
          //<button>, <x>, <y>, <wheel>

          uint8_t msg[] = {  0x00, 0x00, 0x01};

          inputMouse->setValue(msg,3);
          inputMouse->notify();
          delay(10);

     }


  }
  delay(50);
}

I changed pins to match my m5stack, but most important change is reportMap. @senthilkumarh your report map is somehow broken, and android is not strict about it, but windows and iOS wont install device with broken report map.

Open windows bluetooth in settings and add esp32 keyboard:
https://www.windowscentral.com/how-manage-bluetooth-devices-windows-10

Then you have about 5 seconds to switch to notepad and see example message sent from esp32.

@chegewara Thanks for the ESP mouse code you provided.
I copied the same code you provided above post and connected two buttons to PIN 38,39.
after flashing the code to ESP32 its working well with android phone but not working with windows 10.
Please can you tell what I am missing. Is it possible to scroll a page UP/DOWN using ESP HID BT mouse?
Please help.......
The HID keyboard example you provide works in android and in windows10

I tested this code with my windows 10 and it works. I wouldnt post info if i wouldnt check it.
Please check system version (WIN+X -> System). My system version is 1803 compilation version 17134.648. Also you can try this ble library:
BLE.zip

has anyone a functional hid Keyboard code that I could try on my ESP? the examples above don't work for me.

Here is mine:
code

Most of the code I have lifted up from somewhere, so I take no credit for anything except the bugs. It was working correctly until I got bored and dismantled the setup to use it somewhere else. But I will get back to it as it was quite nice - everything fit into a foot pedal!

Thank you @JabberwockPL!
The code works perfectly!

ok I now have one problem left...
how do I use the mute and volume up/Down keys?

I tested this code with my windows 10 and it works. I wouldnt post info if i wouldnt check it.
Please check system version (WIN+X -> System). My system version is 1803 compilation version 17134.648. Also you can try this ble library:
BLE.zip

Thanks for the mouse HID code. The exact code you provided is working in my windows10.
But when I add one extra variable for button in msg[] its not working
//

@chegewara
Thanks for the mouse HID code. The exact code you provided is working in my windows10.
But when I add one extra variable for button in msg[] its not working
//

@senthilkumarh But why do you need another variable for buttons? Probably its a problem, because in this report map already you have variable for buttons. I know only basics about report maps, but in example i provided you have X axis, Y axis and 3 buttons. You can increase it easy up to 8 and im pretty sure you can even increase it further.

I remember now ive read somewhere that windows maybe allowing only mouse with 3 buttons. I am aware that you can find on market mouses with more buttons, but:

  • additional buttons are described as special buttons OR
  • you will need custom windows driver for it.

Sorry but i didnt test it with more than 3 buttons by myself.

@senthilkumarh But why do you need another variable for buttons? Probably its a problem, because in this report map already you have variable for buttons. I know only basics about report maps, but in example i provided you have X axis, Y axis and 3 buttons. You can increase it easy up to 8 and im pretty sure you can even increase it further.

I remember now ive read somewhere that windows maybe allowing only mouse with 3 buttons. I am aware that you can find on market mouses with more buttons, but:

* additional buttons are described as special buttons OR

* you will need custom windows driver for it.

Sorry but i didnt test it with more than 3 buttons by myself.

In you code you mentioned as below
Serial.println("mouse Scroll UP");

There is no wheel in this report map. Check this:

       USAGE_MINIMUM(1),    0x1, // <-- buttons start
       USAGE_MAXIMUM(1),    0x3,
       LOGICAL_MINIMUM(1),  0x0,
       LOGICAL_MAXIMUM(1),  0x1,
       REPORT_COUNT(1),   0x3,
       REPORT_SIZE(1),    0x1,
       0x80|0x01,        0x2,    // (Data, Variable, Absolute), ;3 button bits  
       REPORT_COUNT(1),   0x1,
       REPORT_SIZE(1),    0x5,
       0x80|0x01,        0x1,    //(Constant), ;5 bit padding  <--- buttons end with 5 bits padding
       USAGE_PAGE(1),     0x1,    //(Generic Desktop),
       USAGE(1),        0x30,  // <--- X or Y axis
       USAGE(1),        0x31,  // <--- X or Y axis
       LOGICAL_MINIMUM(1),  0x81,
       LOGICAL_MAXIMUM(1),  0x7f,

If you look at Keil's reports, he has 5 button mouse with a scroll wheel and tilt. His USAGE_MAXIMUM and REPORT_COUNT for buttons are 5.

There is no wheel in this report map. Check this:

       USAGE_MINIMUM(1),    0x1, // <-- buttons start
       USAGE_MAXIMUM(1),    0x3,
       LOGICAL_MINIMUM(1),  0x0,
       LOGICAL_MAXIMUM(1),  0x1,
       REPORT_COUNT(1),   0x3,
       REPORT_SIZE(1),    0x1,
       0x80|0x01,        0x2,    // (Data, Variable, Absolute), ;3 button bits  
       REPORT_COUNT(1),   0x1,
       REPORT_SIZE(1),    0x5,
       0x80|0x01,        0x1,    //(Constant), ;5 bit padding  <--- buttons end with 5 bits padding
       USAGE_PAGE(1),     0x1,    //(Generic Desktop),
       USAGE(1),        0x30,  // <--- X or Y axis
       USAGE(1),        0x31,  // <--- X or Y axis
       LOGICAL_MINIMUM(1),  0x81,
       LOGICAL_MAXIMUM(1),  0x7f,

@chegewara Thank you very much for you support. I am able to do mouse move up/down and mouse left/right button function now.

This works for me with windows 10. There is minor issue, is not sending capital H, but i have no time to check it why. Its with txt extension because github does not allow to send ino file.
HID_keyboard.ino.txt

@chegewara - Thank you for your code. I am testing it, but cannot fix the capital H issue! I think the first byte in the packet is malformed, maybe a problem with the esp32 BLE stack??

Can you tell me if this works properly on your setup now? I am using the arduino 1.0.1 version of the esp32 libraries.

Hi, the issue is in example code in loop. This is correct code:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"
#include <driver/adc.h>

BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

uint8_t buttons = 0;
uint8_t button1 = 0;
uint8_t button2 = 0;
uint8_t button3 = 0;
bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer){
    connected = true;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(true);
  }

  void onDisconnect(BLEServer* pServer){
    connected = false;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(false);
  }
};

/*
 * This callback is connect with output report. In keyboard output report report special keys changes, like CAPSLOCK, NUMLOCK
 * We can add digital pins with LED to show status
 * bit 0 - NUM LOCK
 * bit 1 - CAPS LOCK
 * bit 2 - SCROLL LOCK
 */
 class MyOutputCallbacks : public BLECharacteristicCallbacks {
 void onWrite(BLECharacteristic* me){
    uint8_t* value = (uint8_t*)(me->getValue().c_str());
    ESP_LOGI(LOG_TAG, "special keys: %d", *value);
  }
};

void taskServer(void*){


    BLEDevice::init("ESP32-keyboard");
    BLEServer *pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyCallbacks());

    hid = new BLEHIDDevice(pServer);
    input = hid->inputReport(1); // <-- input REPORTID from report map
    output = hid->outputReport(1); // <-- output REPORTID from report map

    output->setCallbacks(new MyOutputCallbacks());

    std::string name = "chegewara";
    hid->manufacturer()->setValue(name);

    hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
    hid->hidInfo(0x00,0x02);

  BLESecurity *pSecurity = new BLESecurity();
//  pSecurity->setKeySize();
  pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);

    const uint8_t report[] = {
      USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
      USAGE(1),           0x06,       // Keyboard
      COLLECTION(1),      0x01,       // Application
      REPORT_ID(1),       0x01,        //   Report ID (1)
      USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
      USAGE_MINIMUM(1),   0xE0,
      USAGE_MAXIMUM(1),   0xE7,
      LOGICAL_MINIMUM(1), 0x00,
      LOGICAL_MAXIMUM(1), 0x01,
      REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
      REPORT_COUNT(1),    0x08,
      HIDINPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
      REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
      REPORT_SIZE(1),     0x08,
      HIDINPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
      REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
      REPORT_SIZE(1),     0x08,
      LOGICAL_MINIMUM(1), 0x00,
      LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
      USAGE_MINIMUM(1),   0x00,
      USAGE_MAXIMUM(1),   0x65,
      HIDINPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
      REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
      REPORT_SIZE(1),     0x01,
      USAGE_PAGE(1),      0x08,       //   LEDs
      USAGE_MINIMUM(1),   0x01,       //   Num Lock
      USAGE_MAXIMUM(1),   0x05,       //   Kana
      HIDOUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
      REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
      REPORT_SIZE(1),     0x03,
      HIDOUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
      END_COLLECTION(0)
    };

    hid->reportMap((uint8_t*)report, sizeof(report));
    hid->startServices();

    BLEAdvertising *pAdvertising = pServer->getAdvertising();
    pAdvertising->setAppearance(HID_KEYBOARD);
    pAdvertising->addServiceUUID(hid->hidService()->getUUID());
    pAdvertising->start();
    hid->setBatteryLevel(7);

    ESP_LOGD(LOG_TAG, "Advertising started!");
    delay(portMAX_DELAY);

};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  pinMode(38, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(38), clickNumLock, CHANGE);   // Num Lock
  pinMode(39, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(39), clickCapsLock, CHANGE);   // Caps Lock
  pinMode(37, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(37), clickScrollLock, CHANGE);   // Scroll Lock

  xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

void loop() {

  if(connected){

    vTaskDelay(5000);
    const char* hello = "Hello world from esp32 hid keyboard!!!\n";

    while(*hello){
      KEYMAP map = keymap[(uint8_t)*hello];
      Serial.println(buttons);
      uint8_t msg[] = {map.modifier | buttons, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0};  // <--- fixed line
      input->setValue(msg,sizeof(msg));
      input->notify();
      hello++;
      uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};

      input->setValue(msg1,sizeof(msg1));
      input->notify();
      delay(10);
    }
  }
  delay(50);
}

IRAM_ATTR void clickNumLock(){
  button1 = buttons&0x01;
  button1 != button1;
  buttons = button1<<0;
}
IRAM_ATTR void clickCapsLock(){
  button2 = buttons&0x02;
  button2 != button2;  
  buttons = button2<<1;
}
IRAM_ATTR void clickScrollLock(){
  button3 = buttons&0x04;
  button3 != button3;  
  buttons = button3<<2;
}

Ahh, thank you very much for the correction - It is working well!
@chegewara would you mind looking at a piece of HID code? Is the email on your profile the right one?

Tried everything but don't know how to make it workable. Device is recognized with iOS as hid and with correct name. And nothing...
Maybe could you post an example of code which just prints "hello" with esp32 to device by itself (without of additional components like buttons)?

Men how to deal with language/locale if esp32 is connected to iOS? It chooses very random language! My code example:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

const uint8_t keyboardHidDescriptor[] = {
  0x05, 0x0c,                    // USAGE_PAGE (Consumer Devices)
  0x09, 0x01,                    // USAGE (Consumer Control)
  0xa1, 0x01,                    // COLLECTION (Application)
  0x85, 0x01,                    //   REPORT_ID (1)
  0x19, 0x00,                    //   USAGE_MINIMUM (Unassigned)
  0x2a, 0x3c, 0x02,              //   USAGE_MAXIMUM (AC Format)
  0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  0x26, 0x3c, 0x02,              //   LOGICAL_MAXIMUM (572)
  0x95, 0x01,                    //   REPORT_COUNT (1)
  0x75, 0x10,                    //   REPORT_SIZE (16)
  0x81, 0x00,                    //   INPUT (Data,Var,Abs)
  0xc0,                          // END_COLLECTION
  0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  0x09, 0x06,                    // USAGE (Keyboard)
  0xa1, 0x01,                    // COLLECTION (Application)
  0x85, 0x02,                    //   REPORT_ID (2)
  0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
  0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
  0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
  0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  0x75, 0x01,                    //   REPORT_SIZE (1)
  0x95, 0x08,                    //   REPORT_COUNT (8)
  0x81, 0x02,                    //   INPUT (Data,Var,Abs)
  0x95, 0x01,                    //   REPORT_COUNT (1)
  0x75, 0x08,                    //   REPORT_SIZE (8)
  0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
  0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
  0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
  0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
  0xc0                           // END_COLLECTION
};


typedef struct
{
 //uint8_t  reportId;                                 // Report ID = 0x02 (2)
  // Collection: CA:ConsumerControl
  uint16_t ConsumerControl;                          // Value = 0 to 572
} inputConsumer_t;

static uint8_t idleRate;           /* in 4 ms units */

typedef struct
{
// uint8_t  reportId;                                 // Report ID = 0x02 (2)
  // Collection: CA:Keyboard
  uint8_t  KB_KeyboardKeyboardLeftControl  : 1;       // Usage 0x000700E0: Keyboard Left Control, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardLeftShift  : 1;         // Usage 0x000700E1: Keyboard Left Shift, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardLeftAlt    : 1;           // Usage 0x000700E2: Keyboard Left Alt, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardLeftGui    : 1;           // Usage 0x000700E3: Keyboard Left GUI, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightControl : 1;      // Usage 0x000700E4: Keyboard Right Control, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightShift   : 1;        // Usage 0x000700E5: Keyboard Right Shift, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightAlt   : 1;          // Usage 0x000700E6: Keyboard Right Alt, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightGui   : 1;          // Usage 0x000700E7: Keyboard Right GUI, Value = 0 to 1
  uint8_t  Key;                                 // Value = 0 to 101
} inputKeyboard_t;

static inputConsumer_t consumer_Report{};
static inputKeyboard_t keyboard_report{}; // sent to PC

BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;
BLECharacteristic* inputVolume;
BLECharacteristic* outputVolume;
bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer){
    connected = true;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(true);

    BLE2902* descv = (BLE2902*)inputVolume->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    descv->setNotifications(true);
  }

  void onDisconnect(BLEServer* pServer){
    connected = false;
    BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(false);

    BLE2902* descv = (BLE2902*)inputVolume->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    descv->setNotifications(false);
  }
};


  const int ledPin = A12;


void taskServer(void*){
  BLEDevice::init("ESP32");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyCallbacks());

  hid = new BLEHIDDevice(pServer);
  inputVolume = hid->inputReport(1); // <-- input REPORTID from report map
  outputVolume = hid->outputReport(1); // <-- output REPORTID from report map


  input = hid->inputReport(2); // <-- input REPORTID from report map
  output = hid->outputReport(2); // <-- output REPORTID from report map

  std::string name = "ElectronicCats";
  hid->manufacturer()->setValue(name);

  hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
  hid->hidInfo(0x00,0x02);

  hid->reportMap((uint8_t*)keyboardHidDescriptor, sizeof(keyboardHidDescriptor));
  hid->startServices();


  BLESecurity *pSecurity = new BLESecurity();
//  pSecurity->setKeySize();
  pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);


  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->setAppearance(HID_KEYBOARD);
  pAdvertising->addServiceUUID(hid->hidService()->getUUID());
  pAdvertising->start();
  hid->setBatteryLevel(7);

  delay(portMAX_DELAY);
}


void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  pinMode(ledPin, OUTPUT);

//  keyboard_report.reportId = 0x02;
  //consumer_Report.reportId = 0x01;

  xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

int writtenCount = 0;

void loop() {
//  delay(4000);
//if(connected){
//
//    const char* hello = "Hello world from esp32 hid keyboard!!!\n";
//
//    while(*hello){
//      KEYMAP map = keymap[(uint8_t)*hello];
//      uint8_t msg[] = {map.modifier , 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0};
//      input->setValue(msg,sizeof(msg));
//      input->notify();
//      hello++;
//      uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
//
//      input->setValue(msg1,sizeof(msg1));
//      input->notify();
//      delay(10);
//    }
//  }
//  delay(50);

  // put your main code here, to run repeatedly:
  if(connected && (writtenCount < 2)){
    writtenCount++;
    const char* hello = "Hello123";
    while(*hello){
      inputKeyboard_t a{};
      KEYMAP map = keymap[(uint8_t)*hello];
      if (map.modifier == 2) {
        a.KB_KeyboardKeyboardLeftShift = 1;
      }
      a.Key = map.usage;
      input->setValue((uint8_t*)&a,sizeof(a));
      input->notify();
      input->setValue((uint8_t*)(&keyboard_report), sizeof(keyboard_report));
      input->notify();

      hello++;
      delay(10);
    }
//    inputKeyboard_t a{};
//    a.Key = 'a';//random(0x02,0x27);
//     input->setValue((uint8_t*)&a,sizeof(a));
//    input->notify();
//    input->setValue((uint8_t*)(&keyboard_report), sizeof(keyboard_report));
//    input->notify();

    delay(1000);
    digitalWrite(ledPin, LOW);
  }
}

@Gargo , I was wondering if you might be able to help me to adapt this code for a different project I am working on... I would like to add switches to several of the inputs and have the ESP32 send keyboard strokes via BLE to control page forward / back on an ipad. Thanks!!

Hardware wise, how would you connect multiple switches to the esp32 ? Would it just be a case of soldering one line to an io contact and one to ground? Then polling the io? Bit of a noob here and trying to make a wireless arcade controller using his keyboard key codes because they’re fairly universal across devices.

Hello altogether,
I just wonder if someone of you made it the other way round.
I need to use a HID (hz-2749-675 auvidio mini game controller) with an arduino ESP32.
The game controller works without any installation with my android phone right after bluetooth connection.
It is recognices by my ESP32 as an BLE device and I get a connection, but there are no services I could read to get the joystick and button values.

If there is another issue handling this, please tell me.
If not, can you help me?

Roland

Hi,
you will need to write driver to find and read data from particular characteristic, then when you have byte array you will need to parse it. To do it you have to understand how HID works or at least report map and get all necessary info from report map of this device.

Hi Chegewara,
thanks for your fast answer and your help.
The Problem is, I get a GATT unknown Event while reading:

[D][BLEClient.cpp:396] getService(): << getService: found the service with uuid:
00001812-0000-1000-8000-00805f9b34fb
[V][BLEUtils.cpp:1284] dumpGattClientEvent(): GATT Event: Unknown

  • Found our service: [V][BLEUtils.cpp:952] gattClientEventTypeToString(): Unknown GATT Client event type: 18
    00001812-0000-1000-8000-00805f9b34fb

1812 is the HID service number and I would love to read the byte array to parse my values,
but I do not get anything

Dont worry about such logs, its all fine:
GATT Event: Unknown

Ok, I set debug to "info" and got rid of the log,
but I do not Read a value:

if(pRemoteCharacteristic->canRead()) {
Serial.println("can read...");
std::string value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}

The terminal output shows the "The characteristic value was: " line but no value.
So obviously I could read but the value is empty.
(that's the reason I tried to set debug higher)

Probably because all values or first one is 0x0, and you are trying to print null terminated string.

If I change to
while(pRemote....
instead of "if"
I get an endless loop printing only my statements but no value...

The code provided by @chegewara works successfully on Windows but not on android. I am trying to implement using the arrow keys. Using a BLE scanner application I will verify that the ESP is sending the ascii code corresponding to the letters 'O' and 'P' to the android instead of the arrows. I did not understand the reason.
The map of Windows keys are different from android? What is the ascii of arrows on android?
Has anyone managed to make the code work on android?

As far as i know, HID is a standard, so keycodes also are standardized and should be the same on all devices that support it.
The problem with android is that HID driver does not strictly follow bluetooth specs.

If you can, i would suggest to perform test and send all possible keycodes one by one and see if any will work as arrow.

@chegewara thanks for the contact. I already tested several codes but the android only understands 0 (zero). Missing some detail in the code for the android recognize the keys. Were you able to run the code successfully on android?

Sure, i could run and test esp32 HID with android. My android smartphone could print test text without problem and as far as i know some users also could use keys from consumer page.

Very good @chegewara . So what was the modification you did in the code to run the arrow keys?

I didnt try to send arrow keys, just regular text.
Just to be clear, what values you are trying to send for arrows?

This is the problem. Letters and numbers work. The arrow keys do not. I replaced the text input in the original code by pressing buttons. I did it this way:

void loop () {
     if (connected) {
        // RIGHT
        if (digitalRead(BtRight) == HIGH) {
            uint8_t msg[] = {0x0, 0x0, 0x4F, 0x0,0x0,0x0,0x0,0x0};
            input->setValue(msg, sizeof(msg));
            input->notify();
            uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
            input->setValue(msg1, sizeof (msg1));
            input->notify ();
        }
        
        // LEFT
        if (digitalRead(BtLeft) == HIGH) {
            uint8_t msg[] = {0x0, 0x0, 0x50, 0x0,0x0,0x0,0x0,0x0};
            input->setValue(msg, sizeof (msg));
            input->notify ();
            uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
            input->setValue(msg1, sizeof (msg1));
            input->notify ();
        }
    }
    delay (50);
}

The 0x4F(right) and 0x50(left) values ​​work only on Windows. I've already replaced the 0x4F with 0xD7, 0x7A, 0x07, 0x4D, 0x27 and 0x74 based on various keyboard layouts I found but did not succeed. On android 79(0x4F) and 80(0x50) display the letters 'O' and 'P'. Coincidentally, 79 and 80 are the Keycodes of 'O' and 'P'. It looks like android converts ACSII to Keycode. So I tested 37(0x25) and 39(0x27) which are the keycodes of the right and left arrow keys, but they did not work either.
I also took a test by turning off the lines:

uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0};
input->setValue(msg1, sizeof (msg1));
input->notify ();

... but without success.
I think the problem may be in the part of the code that refers to the "reportMap". The android may not be understanding.
So am I doing a lot of bullshit or am I on the right track? lol
I do not know what I can do anymore.

@nkolban Thanks for un-archiving the repo.
@chegewara Okay, lets continue the discussion here, so other people can benefit.

Here you have values for X and Y:

LOGICAL_MINIMUM(1), 0x81,
LOGICAL_MAXIMUM(1), 0x7f,

It is 2 compliment value where 0x7f is highest right and down value.

BTW looks like Kolban's repository is no longer archived.

I'm not sure if I can follow. From what I understand, you are saying that 0x81-0x7f is the range of possible values for the X and Y byte, right? But I am looking for a way to scroll using the scroll and not for a way to move the mouse pointer.

When I run:

uint8_t msg[] = {  0x00, 0x81, 0x81 };
inputMouse->setValue(msg,3);

it does indeed move the mouse pointer to the top/left by quite a bit.

But when I try to run:

uint8_t msg[] = { 0x00, 0x00, 0x00, 0x81 };
inputMouse->setValue(msg,3);

Nothing happens at all. I was hoping to see it scroll up or down.

Hi @T-vK
sorry, yes, now i know what you wannt and why you cant achieve it.
This report map is for mouse without wheel, only X, Y and 3 buttons:

const uint8_t reportMapMouse[] = {

USAGE_PAGE(1),       0x01,
      USAGE(1),         0x02,
       COLLECTION(1),     0x01,
       REPORT_ID(1),  0x01,
       USAGE(1),        0x01,
       COLLECTION(1),     0x00,
       USAGE_PAGE(1),     0x09,
       USAGE_MINIMUM(1),    0x1,
       USAGE_MAXIMUM(1),    0x3,
       LOGICAL_MINIMUM(1),  0x0,
       LOGICAL_MAXIMUM(1),  0x1,
       REPORT_COUNT(1),   0x3,
       REPORT_SIZE(1),    0x1,
       0x80|0x01,        0x2,    // (Data, Variable, Absolute), ;3 button bits
       REPORT_COUNT(1),   0x1,
       REPORT_SIZE(1),    0x5,
       0x80|0x01,        0x1,    //(Constant), ;5 bit padding
       USAGE_PAGE(1),     0x1,    //(Generic Desktop),
       USAGE(1),        0x30,
       USAGE(1),        0x31,
       LOGICAL_MINIMUM(1),  0x81,
       LOGICAL_MAXIMUM(1),  0x7f,
       REPORT_SIZE(1),    0x8,
       REPORT_COUNT(1),   0x2,
       0x80|0x01,        0x6,    //(Data, Variable, Relative), ;2 position bytes (X & Y)
       END_COLLECTION(0),
      END_COLLECTION(0)
};

I didnt test it, but this should work:

const uint8_t reportMapMouse[] = {

USAGE_PAGE(1),       0x01,
      USAGE(1),         0x02,
       COLLECTION(1),     0x01,
       REPORT_ID(1),  0x01,
       USAGE(1),        0x01,
       COLLECTION(1),     0x00,
       USAGE_PAGE(1),     0x09,
       USAGE_MINIMUM(1),    0x1,
       USAGE_MAXIMUM(1),    0x3,
       LOGICAL_MINIMUM(1),  0x0,
       LOGICAL_MAXIMUM(1),  0x1,
       REPORT_COUNT(1),   0x3,
       REPORT_SIZE(1),    0x1,
       0x80|0x01,        0x2,    // (Data, Variable, Absolute), ;3 button bits
       REPORT_COUNT(1),   0x1,
       REPORT_SIZE(1),    0x5,
       0x80|0x01,        0x1,    //(Constant), ;5 bit padding
       USAGE_PAGE(1),     0x1,    //(Generic Desktop),
       USAGE(1),        0x30,
       USAGE(1),        0x31,
       USAGE(1),        0x38,
       LOGICAL_MINIMUM(1),  0x81,
       LOGICAL_MAXIMUM(1),  0x7f,
       REPORT_SIZE(1),    0x8,
       REPORT_COUNT(1),   0x3,
       0x80|0x01,        0x6,    //(Data, Variable, Relative), ;2 position bytes (X & Y)
       END_COLLECTION(0),
      END_COLLECTION(0)
};

Thank you so much! Using the report map you provided I was able to scroll up/down using byte 4 (I also had to increase the second parameter of the setValue method by one.)

For anyone who's interested:

          Serial.println("mouse Scroll DOWN");
          //<button>, <x>, <y>, <wheel>

          uint8_t msg[] = { 0x00, 0x00, 0x00, -0x01 };

          inputMouse->setValue(msg,4);
          inputMouse->notify();

I have now tried to make an easy-to-use library out of the code that you provided. https://github.com/T-vK/ESP32-BLE-Mouse

Unfortunately my C++ skills are not the greatest. When I try to compile the example I get the following errors:

/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In constructor 'BleMouse::BleMouse()':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:14:20: error: no matching function for call to 'BleConnectionStatus::BleConnectionStatus()'
 BleMouse::BleMouse() {
                    ^
In file included from /home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:11:0:
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:12:3: note: candidate: BleConnectionStatus::BleConnectionStatus(BLECharacteristic*)
   BleConnectionStatus(BLECharacteristic* inputMouse);
   ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:12:3: note:   candidate expects 1 argument, 0 provided
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note: candidate: constexpr BleConnectionStatus::BleConnectionStatus(const BleConnectionStatus&)
 class BleConnectionStatus : public BLEServerCallbacks {
       ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note:   candidate expects 1 argument, 0 provided
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note: candidate: constexpr BleConnectionStatus::BleConnectionStatus(BleConnectionStatus&&)
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note:   candidate expects 1 argument, 0 provided
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:15:26: error: no match for 'operator=' (operand types are 'BleConnectionStatus' and 'BleConnectionStatus*')
   this->connectionStatus = new BleConnectionStatus(this->inputMouse);
                          ^
In file included from /home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:11:0:
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note: candidate: BleConnectionStatus& BleConnectionStatus::operator=(const BleConnectionStatus&)
 class BleConnectionStatus : public BLEServerCallbacks {
       ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note:   no known conversion for argument 1 from 'BleConnectionStatus*' to 'const BleConnectionStatus&'
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note: candidate: BleConnectionStatus& BleConnectionStatus::operator=(BleConnectionStatus&&)
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleConnectionStatus.h:10:7: note:   no known conversion for argument 1 from 'BleConnectionStatus*' to 'BleConnectionStatus&&'
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In member function 'void BleMouse::init()':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:19:57: error: invalid use of non-static member function
   xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
                                                         ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In member function 'void BleMouse::taskServer(void*)':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:25:47: error: no matching function for call to 'BLEServer::setCallbacks(BleConnectionStatus&)'
   pServer->setCallbacks(this->connectionStatus);
                                               ^
In file included from /home/fedora/.arduino15/packages/esp32/hardware/esp32/1.0.2/libraries/BLE/src/BLEDevice.h:18:0,
                 from /home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:1:
/home/fedora/.arduino15/packages/esp32/hardware/esp32/1.0.2/libraries/BLE/src/BLEServer.h:69:18: note: candidate: void BLEServer::setCallbacks(BLEServerCallbacks*)
  void            setCallbacks(BLEServerCallbacks* pCallbacks);
                  ^
/home/fedora/.arduino15/packages/esp32/hardware/esp32/1.0.2/libraries/BLE/src/BLEServer.h:69:18: note:   no known conversion for argument 1 from 'BleConnectionStatus' to 'BLEServerCallbacks*'
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:80:12: error: 'LOG_TAG' was not declared in this scope
   ESP_LOGD(LOG_TAG, "Advertising started!");
            ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:80:43: error: 'ESP_LOGD' was not declared in this scope
   ESP_LOGD(LOG_TAG, "Advertising started!");
                                           ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:81:22: error: 'delay' was not declared in this scope
   delay(portMAX_DELAY);
                      ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In member function 'bool BleMouse::isConnected()':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:85:32: error: base operand of '->' has non-pointer type 'BleConnectionStatus'
   return this->connectionStatus->connected;
                                ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In function 'void rawAction(uint8_t, char)':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:89:3: error: invalid use of 'this' in non-member function
   this->inputMouse->setValue(msg, msgSize);
   ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:90:3: error: invalid use of 'this' in non-member function
   this->inputMouse->notify();
   ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In member function 'void BleMouse::scrollDown(char)':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:96:27: error: invalid conversion from 'uint8_t* {aka unsigned char*}' to 'uint8_t {aka unsigned char}' [-fpermissive]
     this->rawAction(msg, 4);
                           ^
In file included from /home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:12:0:
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.h:18:8: note:   initializing argument 1 of 'void BleMouse::rawAction(uint8_t, char)'
   void rawAction(uint8_t msg, char msgSize);
        ^
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp: In member function 'void BleMouse::scrollUp(char)':
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:103:27: error: invalid conversion from 'uint8_t* {aka unsigned char*}' to 'uint8_t {aka unsigned char}' [-fpermissive]
     this->rawAction(msg, 4);
                           ^
In file included from /home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.cpp:12:0:
/home/fedora/Arduino/libraries/ESP32-BLE-Mouse/BleMouse.h:18:8: note:   initializing argument 1 of 'void BleMouse::rawAction(uint8_t, char)'
   void rawAction(uint8_t msg, char msgSize);
        ^
exit status 1
Error compiling for board ESP32 Dev Module.

Any help would be highly appreciated.

Edit: All errors are resolved now.

@chegewara
I have successfully created an easy to use BLE Mouse library now:
https://github.com/T-vK/ESP32-BLE-Mouse

I made the interface so that it is practically identical to the built-in Mouse class API.

But there are still some things that I haven't figured out yet:
What number do I have to set in order to press each button?
(Left, Right, Middle, Back, Forward)
I've tried this hoping to get a left click but it didn't work:

uint8_t msg[] = { 0x01, 0x00, 0x00, 0x00, 0x00 };
inputMouse->setValue(msg,5);

And what do I have to do to scroll horizontally?
I've tried this and it didn't work:

uint8_t msg[] = { 0x00, 0x00, 0x00, 0x00, -0x01 };
inputMouse->setValue(msg,4);

Great decision to reactivate the topic. Could you make the code work with the arrow keys on android? What modifications did you make? I posted above the changes I made but was unsuccessful.

@chegewara I found a new report map on the Internet. And I am now able to use all 5 buttons, scroll up/down, scroll left/right and move the mouse to the left/right. But I just can't figure out how to move the mouse up/down with this new report map. Can you help me with that?

This is working so far:

uint8_t m[4];
m[0] = _buttons; // buttons
m[1] = x; // move mouse left/right
m[2] = wheel; // scroll up/down
m[3] = hWheel; // scroll left/right
this->inputMouse->setValue(m, 4);
this->inputMouse->notify();

But I can't figure out how to use the y value.

This is the new report map:

//
// Wheel Mouse - simplified version
//
// Input report - 5 bytes
//
// Byte | D7 D6 D5 D4 D3 D2 D1 D0
// ------+---------------------------------------------------------------------
// 0 | 0 0 0 Forward Back Middle Right Left (Button)
// 1 | X
// 2 | Y
// 3 | Vertical Wheel
// 4 | Horizontal (Tilt) Wheel
//
// Feature report - 1 byte
//
// Byte | D7 D6 D5 D4 | D3 D2 | D1 D0
// ------+------------------------------+--------------+----------------
// 0 | 0 0 0 0 | Horizontal | Vertical
// (Resolution multiplier)
//
// Reference
// Wheel.docx in "Enhanced Wheel Support in Windows Vista" on MS WHDC
// http://www.microsoft.com/whdc/device/input/wheel.mspx
//

static const uint8_t _hidReportDescriptor[] = {
  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  0x09, 0x02, // USAGE (Mouse)
  0xa1, 0x01, // COLLECTION (Application)
  0x09, 0x02, // USAGE (Mouse)
  0xa1, 0x02, // COLLECTION (Logical)
  0x09, 0x01, // USAGE (Pointer)
  0xa1, 0x00, // COLLECTION (Physical)
  // ------------------------------ Buttons
  0x05, 0x09, // USAGE_PAGE (Button)
  0x19, 0x01, // USAGE_MINIMUM (Button 1)
  0x29, 0x05, // USAGE_MAXIMUM (Button 5)
  0x15, 0x00, // LOGICAL_MINIMUM (0)
  0x25, 0x01, // LOGICAL_MAXIMUM (1)
  0x75, 0x01, // REPORT_SIZE (1)
  0x95, 0x05, // REPORT_COUNT (5)
  0x81, 0x02, // INPUT (Data,Var,Abs)
  // ------------------------------ Padding
  0x75, 0x03, // REPORT_SIZE (3)
  0x95, 0x01, // REPORT_COUNT (1)
  0x81, 0x03, // INPUT (Cnst,Var,Abs)
  // ------------------------------ X,Y position
  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  0x09, 0x30, // USAGE (X)
  0x09, 0x31, // USAGE (Y)
  0x15, 0x81, // LOGICAL_MINIMUM (-127)
  0x25, 0x7f, // LOGICAL_MAXIMUM (127)
  0x75, 0x08, // REPORT_SIZE (8)
  0x95, 0x02, // REPORT_COUNT (2)
  0x81, 0x06, // INPUT (Data,Var,Rel)
  0xa1, 0x02, // COLLECTION (Logical)
  // ------------------------------ Vertical wheel res multiplier
  0x09, 0x48, // USAGE (Resolution Multiplier)
  0x15, 0x00, // LOGICAL_MINIMUM (0)
  0x25, 0x01, // LOGICAL_MAXIMUM (1)
  0x35, 0x01, // PHYSICAL_MINIMUM (1)
  0x45, 0x04, // PHYSICAL_MAXIMUM (4)
  0x75, 0x02, // REPORT_SIZE (2)
  0x95, 0x01, // REPORT_COUNT (1)
  0xa4, // PUSH
  0xb1, 0x02, // FEATURE (Data,Var,Abs)
  // ------------------------------ Vertical wheel
  0x09, 0x38, // USAGE (Wheel)
  0x15, 0x81, // LOGICAL_MINIMUM (-127)
  0x25, 0x7f, // LOGICAL_MAXIMUM (127)
  0x35, 0x00, // PHYSICAL_MINIMUM (0) - reset physical
  0x45, 0x00, // PHYSICAL_MAXIMUM (0)
  0x75, 0x08, // REPORT_SIZE (8)
  0x81, 0x06, // INPUT (Data,Var,Rel)
  0xc0, // END_COLLECTION
  0xa1, 0x02, // COLLECTION (Logical)
  // ------------------------------ Horizontal wheel res multiplier
  0x09, 0x48, // USAGE (Resolution Multiplier)
  0xb4, // POP
  0xb1, 0x02, // FEATURE (Data,Var,Abs)
  // ------------------------------ Padding for Feature report
  0x35, 0x00, // PHYSICAL_MINIMUM (0) - reset physical
  0x45, 0x00, // PHYSICAL_MAXIMUM (0)
  0x75, 0x04, // REPORT_SIZE (4)
  0xb1, 0x03, // FEATURE (Cnst,Var,Abs)
  // ------------------------------ Horizontal wheel
  0x05, 0x0c, // USAGE_PAGE (Consumer Devices)
  0x0a, 0x38, 0x02, // USAGE (AC Pan)
  0x15, 0x81, // LOGICAL_MINIMUM (-127)
  0x25, 0x7f, // LOGICAL_MAXIMUM (127)
  0x75, 0x08, // REPORT_SIZE (8)
  0x81, 0x06, // INPUT (Data,Var,Rel)
  0xc0, // END_COLLECTION
  0xc0, // END_COLLECTION
  0xc0, // END_COLLECTION
  0xc0 // END_COLLECTION
};

I am very busy today, but i will look on it.

@chegewara I hope you haven't invested time on my issue yet. I just had a hard to explain issue on my Android phone that caused it to scroll up/down instead of moving the pointer up/down and some apps didn't support all the mouse features.... it doesn't really matter. It works now:

    uint8_t m[5];
    m[0] = _buttons;
    m[1] = x;
    m[2] = y;
    m[3] = wheel;
    m[4] = hWheel;
    this->inputMouse->setValue(m, 5);
    this->inputMouse->notify();

The BLE Mouse library should be fully functional now.

If you don't mind @chegewara I would like to add a free license to the project. But I need your permission since it is based on your code.

@T-vK if you are using this library then here is license:
https://github.com/nkolban/esp32-snippets/blob/master/LICENSE

@chegewara I'm not sure what you mean by "if you are using this library". I was asking because I use a lot of this code if I can have your permission add a free license to the mouse and keyboard library I wrote.

I have wrapped the Bluetooth keyboard code into a nice library now btw. duke2421 helped A LOT making this happen.
It should work fine for the most part, but at the moment the library always assumes that you have a US keyboard layout.

Bluetooth keyboard: https://github.com/T-vK/ESP32-BLE-Keyboard
Bluetooth mouse: https://github.com/T-vK/ESP32-BLE-Mouse

@T-vK Initially this BLE library has been created by Neil Kolban and by now its open source community library. I dont know if and how you can change Apache license. I only added some functionalities, and one of them is HID class. All my code available on github public repositories are open source and free to use.

@chegewara Yes, Neil Kolban wrote the BLE library, but his code is not part of my repository (I merely have a dependency on it). But the code that you posted here is more or less directly included. Assuming that you are the author of that piece of code (not of the includes), I'd only need your persission. Preferrably I would go with an MIT license which is very open and commonly used.

To be honest i really dont know how much code of this code is mine. I only know i wrote HID class in this library, and as you can see in that code is also this link, so most likely report map is coming from there.
If you wish you can use all my code with MIT license.

@chegewara
Hi,
Thank you for all this useful conversation!
I'm currently trying to send HID key commands through ESP32 to my ios phone, it connects and recognizes as an HID device(Keyboard disappears when trying to type), but key commands couldn't be sent. It works perfectly fine on both my android phone and windows laptop.

Based on the conversation, adding access permission to all characteristic seems to be the solution to my problem which is from here https://github.com/asterics/esp32_mouse_keyboard/issues/4#issuecomment-398674047
But after I added it, it still wouldn't work. I tried using the library you made which is from here
https://github.com/chegewara/esp32-snippets/tree/multi_connect/cpp_utils
But it couldn't even connect to Bluetooth for neither phone with your library. I feel like I'm missing something. I would greatly appreciate if you have any insight into this problem, thanks!

The following is the code I'm running.

include

include

include "BLE2902.h"

include "BLEHIDDevice.h"

include "HIDTypes.h"

include "HIDKeyboardTypes.h"

include

include

BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

uint8_t buttons = 0;
uint8_t button1 = 0;
uint8_t button2 = 0;
uint8_t button3 = 0;
bool connected = false;

class MyCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
connected = true;
BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
desc->setNotifications(true);
}

void onDisconnect(BLEServer* pServer) {
  connected = false;
  BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
  desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  desc->setNotifications(false);
}

};

/*
This callback is connect with output report. In keyboard output report report special keys changes, like CAPSLOCK, NUMLOCK
We can add digital pins with LED to show status
bit 0 - NUM LOCK
bit 1 - CAPS LOCK
bit 2 - SCROLL LOCK
/
class MyOutputCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic
me) {
uint8_t* value = (uint8_t*)(me->getValue().c_str());
ESP_LOGI(LOG_TAG, "special keys: %d", *value);
}
};

void taskServer(void*) {

BLEDevice::init("Please Work");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyCallbacks());

hid = new BLEHIDDevice(pServer);
input = hid->inputReport(1); // <-- input REPORTID from report map
output = hid->outputReport(1); // <-- output REPORTID from report map

output->setCallbacks(new MyOutputCallbacks());

std::string name = "ElectronicCats";
hid->manufacturer()->setValue(name);

hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
hid->hidInfo(0x00, 0x02);

BLESecurity *pSecurity = new BLESecurity();
// pSecurity->setKeySize();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);

const uint8_t report[] = {
USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls
USAGE(1), 0x06, // Keyboard
COLLECTION(1), 0x01, // Application
REPORT_ID(1), 0x01, // Report ID (1)
USAGE_PAGE(1), 0x07, // Kbrd/Keypad
USAGE_MINIMUM(1), 0xE0,
USAGE_MAXIMUM(1), 0xE7,
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0x01,
REPORT_SIZE(1), 0x01, // 1 byte (Modifier)
REPORT_COUNT(1), 0x08,
HIDINPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
REPORT_COUNT(1), 0x01, // 1 byte (Reserved)
REPORT_SIZE(1), 0x08,
HIDINPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
REPORT_COUNT(1), 0x06, // 6 bytes (Keys)
REPORT_SIZE(1), 0x08,
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0x65, // 101 keys
USAGE_MINIMUM(1), 0x00,
USAGE_MAXIMUM(1), 0x65,
HIDINPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
REPORT_SIZE(1), 0x01,
USAGE_PAGE(1), 0x08, // LEDs
USAGE_MINIMUM(1), 0x01, // Num Lock
USAGE_MAXIMUM(1), 0x05, // Kana
HIDOUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
REPORT_COUNT(1), 0x01, // 3 bits (Padding)
REPORT_SIZE(1), 0x03,
HIDOUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
END_COLLECTION(0)
};

hid->reportMap((uint8_t*)report, sizeof(report));
hid->startServices();

BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->setAppearance(HID_KEYBOARD);
pAdvertising->addServiceUUID(hid->hidService()->getUUID());
pAdvertising->start();
hid->setBatteryLevel(7);

ESP_LOGD(LOG_TAG, "Advertising started!");
delay(portMAX_DELAY);

};

void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");

pinMode(12, INPUT_PULLDOWN);
attachInterrupt(digitalPinToInterrupt(12), clickNumLock, HIGH); // Num Lock
pinMode(14, INPUT_PULLDOWN);
attachInterrupt(digitalPinToInterrupt(13), clickCapsLock, HIGH); // Caps Lock
pinMode(32, INPUT_PULLDOWN);
attachInterrupt(digitalPinToInterrupt(32), clickScrollLock, HIGH); // Scroll Lock

xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);
}

void loop() {
if (connected) {

vTaskDelay(5000);
const char* hello = "Hello world from esp32 hid keyboard!!!\n";

while (*hello) {
  KEYMAP map = keymap[(uint8_t) * hello];
  Serial.println(buttons);
  uint8_t msg[] = {0x02, 0x0, 0x04, 0x05, 0x06, 0x0, 0x0, 0x0};
  input->setValue(msg, sizeof(msg));
  input->notify();
  hello++;
  uint8_t msg1[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};

  input->setValue(msg1, sizeof(msg1));
  input->notify();
  delay(10);
}

}

delay(50);
}

IRAM_ATTR void clickNumLock() {
button1 = buttons && 0x01;
button1 != button1;
buttons = button1 << 0;
}
IRAM_ATTR void clickCapsLock() {
button2 = buttons && 0x02;
button2 != button2;
buttons = button2 << 1;
}
IRAM_ATTR void clickScrollLock() {
button3 = buttons && 0x04;
button3 != button3;
buttons = button3 << 2;
}

https://github.com/T-vK/ESP32-BLE-Keyboard

Might be worth checking if this new library works.

Says its untested on iOS. Test it and provide feedback

@lemmingDev

Thanks, I finally made it worked on my ios phone with the GitHub you provided! Very much appreciated!

Great to hear

Hi,
Anybody tried to connect the ESP32 to a Bluetooth 3.0 classic device like the AB shutter 3 button?

I found this linux tutorial and nothing else.
https://shkspr.mobi/blog/2016/02/cheap-bluetooth-buttons-and-linux/

I was no able to find any example for ESP32, the only support I found is in these library:
https://github.com/espressif/arduino-esp32/tree/master/tools/sdk/include/bt

Bests,
G.

Hi, If I have a report descriptor with various report ID's (essentially 1- media control, 2 -keyboard and 3- mouse).
how do I swap between reports. Also how can I send structs rather then a buffer array?

I made a ESP32-BLE-Gamepad library if anyone is interested

https://github.com/lemmingDev/ESP32-BLE-Gamepad

T-vK's mouse library was modified...

@Spookhub

Hi, If I have a report descriptor with various report ID's (essentially 1- media control, 2 -keyboard and 3- mouse).
how do I swap between reports. Also how can I send structs rather then a buffer array?

Take a look at my keyboard library:
https://github.com/T-vK/ESP32-BLE-Keyboard/blob/master/BleKeyboard.cpp#L123-L126

As you can see, I'm simply passing the id I want to access to the inputReport method. You can then simply use the BLECharacteristic object it returns for each call with its setValue and notify methods.

Yes, this is exactly how its designed.

For every report id in report map you need to create characteristic, its either input or output characteristic, with inputReport or outputReport passing reportID as argument:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/BLEHIDDevice.h#L54-L55

This way you can have 3 characteristics, 4 if you want to control CAPS LOCK, NUM LOCK LEDs on esp32:

  • media = inputReport(1)
  • keyboard = inputReport(2)
  • mouse = inputReport(3)

There is something wrong with authentication. Sometimes when trying to connect to ESP you get the message: "Could not pair because of incorrect PIN". Why does it happen?

Thanks for the replies, I appreciate everyone's help. So I am making progress. I am having difficulties in understanding how to get things working. So If I explain what I think is happening please correct me. This may help anyone else coming this way.
The ESP32 core system is/uses https://github.com/espressif/arduino-esp32 , this allows us to create a bluetooth esp32 server.

Then for the core HID system we use nkloban's HID utilities. https://github.com/nkolban/esp32-snippets/tree/master/cpp_utils
This system allows us to send reports, with HID device descriptions and set the device type.
T-vk extended nklobans utilities for a user friendly mouse https://github.com/T-vK/ESP32-BLE-Mouse
@T-vK I am not sure where you define the descriptors e.g USAGE(1), USAGE_PAGE(1) etc.

LemmingDev extended T-vK utils for a user friendly game pad. https://github.com/lemmingDev/ESP32-BLE-Gamepad To get either of these working we need to have each util library in order.

I am creating a Bluetooth and touchpad combo device. Similar to device like this https://www.logitech.com/en-us/product/wireless-touch-keyboard-k400r

To do so I will need create something similar to T-vK but with my own custom HID. So I need the esp32 core, and nkolbans utils snippets.

We can use this to create HID descriptors for keyboards, cursor, game controller or any HID device according to the USB HID https://www.usb.org/document-library/hid-usage-tables-112
Additionally we can use the HID tool to develop the custom HID descriptor, found at, https://www.usb.org/document-library/hid-descriptor-tool
This tool is very good! with examples of various devices.
Nkolban defines various HID types in /src/HIDtypes.h

I will need to create a custom descriptor with the following collections: report id 1 Digitizer (finger) collection, report id 2 Keyboard, report id 3 media keyboard.

Note if you are making keyboard that you intend to use in boot sequence of a computer you need to define your keyboard as a boot keyboard.

I will have to make 3 struct for each report collection.

The digitizer finger is slightly more complicated then mouse, I am trying to reverse engineer the digitizer sample found in the HID developer tool. digi.HID

At the moment I'm a bit stumped in creating the HID report. I am not 100% sure how to create this I understand each report to be send must be 8 bit blocks. Do I create the descriptor as a keyboard application with a physical usage generic device: keyboard, mediakeys and physical usage digitizer finger? or inverse, Physical usage digital, application finger and physical usage generic desktop keyboard and mouse. This blog helps a little bit in creating a HID descriptor https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/ but doesn't tell us much about sending reports.

To send the different reports, we define a uint8_t reportId in the application device structs. Then sending the structs to send(custom descriptor, report struct). How does the HID utils understand its report ID? (with HID->inputreport(reportId)) what is the use for output report. which way is input/output for the host and ble device. I think input is BLE to Host.
t-vk where do you get the defined HID descriptors?

At this moment I have made a keyboard streamer, it takes serial input from the monitor and relays it to connected host Bluetooth device. so you can write from your computer to a ble device.
https://github.com/Spookhub/BLEkeyboardStream/tree/master/BLEkeyboardStream

@T-vK I am not sure where you define the descriptors e.g USAGE(1), USAGE_PAGE(1) etc.

HIDTypes.h

t-vk where do you get the defined HID descriptors?

I wrote them by first reading a few guides on how to write device descriptors like this:
https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/
and using sites like this: https://www.freebsddiary.org/APC/usb_hid_usages.php and by throwing together hid descriptors that I found on the Internet.

You dont have to use that fancy form of report map like in example, you can use something like this (byte array):

uint8_t reportMap[] = {
0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
0x09, 0x02,                    // USAGE (Mouse)
0xa1, 0x01,                    // COLLECTION (Application)
0x09, 0x01,                    //   USAGE (Pointer)
0xa1, 0x00,                    //   COLLECTION (Physical)
0x05, 0x09,                    //     USAGE_PAGE (Button)
0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
0x95, 0x03,                    //     REPORT_COUNT (3)
0x75, 0x01,                    //     REPORT_SIZE (1)
0x81, 0x02,                    //     INPUT (Data,Var,Abs)
0x95, 0x01,                    //     REPORT_COUNT (1)
0x75, 0x05,                    //     REPORT_SIZE (5)
0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
0x09, 0x30,                    //     USAGE (X)
0x09, 0x31,                    //     USAGE (Y)
0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
0x75, 0x08,                    //     REPORT_SIZE (8)
0x95, 0x02,                    //     REPORT_COUNT (2)
0x81, 0x06,                    //     INPUT (Data,Var,Rel)
0xc0,                          //   END_COLLECTION
0xc0                           // END_COLLECTION
};

Input report is from device to client, output report from client to device, feature report both directions.

reportId is stored in 0x2908 descriptor, so client can discover it and recognize which characteristic is tied to which report map part, because all characteristics in HID service has the same UUID.

I am not 100% sure how to create this I understand each report to be send must be 8 bit blocks

not really, in report map you define what size is data send with information, but it has to be 8 bits aligned, thats why sometimes you need to add dummy bits.

To write report map you want either you need to have good knowledge about HID and report maps, or, with small knowledge, to be smart, copy/paste report maps you find and do some experiments.

Hello everyone. About the issue with authentication, I will explain in more detail what happened and how I resolved it, and would like an opinion from you.
My ESP will send only arrow key commands. I am using the T-vK code. Performing connection tests between ESP, an android device, and a Windows PC, sometimes during a reconnection attempt a message appeared "could not connect because of wrong PIN". After that ESP could no longer connect to either android or Windows.
I resolved by changing the following line in BleKeyboard.cpp:
pSecurity-> setAuthenticationMode (_from_ ESP_LE_AUTH_BOND _to_ ESP_LE_AUTH_NO_BOND);
and adding the following line:
pSecurity-> setCapability (ESP_IO_CAP_NONE);
After this modification, I forced several times for the connection error to appear again but always connected successfully.
My question: Can I keep these parameters for "IO capability" and "Authorization request"? I know BLE needs authentication for security and reconnection, but is there a risk that some device will not accept authentication this way?
Thanks for listening.

@ossvr You can keep those changes, it was only example. You can try to remove everything related to BLESecurity (but im not 100% sure, now, just dont remember).

Thanks for the reply chegewara. I will do some more tests.

@chegewara, the modifications I mentioned earlier about authentication...
pSecurity->setAuthenticationMode(ESP_LE_AUTH_NO_BOND);
pSecurity->setCapability(ESP_IO_CAP_NONE);

The ESP no longer generates the PIN to connect this way?

Yes, thats correct.
ESP_IO_CAP_NONE beasically means that ble device (esp32) does not have display and keys or buttons, which means cant display pin or input pin or even use YES/NO to confirm pairing.
ESP_LE_AUTH_NO_BOND does not create paired/bond connection, even with just works (no pin) encryption.

Thanks @chegewara... But is there any risk in that? Can any device deny or block the connection this way? Like I said, my ESP will only send arrow key commands.

With HID device there shouldnt be a risk to connect PCs, laptops, smartphones, but you may find rare situation that peer device will require pin. You need to check in bluetooth HID specification, because i dont remember if its required or optional.
You have to remember that HID specifications is about teens different device types and most of them does not have keypads/keyboards or display, so my guess is it is optional and both devices has to set security option to something different than ESP_IO_CAP_NONE.

Hi @chegewara. I identified a different behavior between standard keyboard commands and T-vK.
When we press it and release a key, the default keyboard sends the "keydown" and "keyup" commands. The T-vK too.
When we press and hold a key, the default keyboard sends "keydown", "keydown", "keydown" commands until we release the key, only then does the default keyboard send "keyup".
T-vK does it differently. It always sends "keydown/keyup", "keydown/keyup", "keydown/keyup" command packets until we release the key. For programming this makes a lot of difference.
Is it possible to change this behavior of T-vK when sending keys? If so, which file can I change the code in? Thanks.

@ossvr Do you have real BLE keyboard? where did you buy it?

You need to change logic where key stroke is send, probably main.c, i dont know his code.

Sorry, I was not clear. Standard keyboard means conventional keyboard, USB. Ok, I will check main.c. Thank you. Will a BLE keyboard always send "keydown/keyup" together? Doesn't work like a conventional keyboard?

BLE keyboard will send key down/up only when you send notify with proper values.

Hm, I haven't actually checked, but
https://github.com/T-vK/ESP32-BLE-Keyboard/blob/master/BleKeyboard.cpp#L316-L355
and
https://github.com/arduino-libraries/Keyboard/blob/master/src/Keyboard.cpp#L226-L265

should be almost identical. I'd be surprised to see such a difference.

In theory, since key strokes are sent with notifications, and when key is pressed down its value should be repeated every some time, you need to keep notify() in loop with delay between 100-300ms, which should let to repeat ~3-10 repeats per second. Then you should stop sending notifications when no key pressed down, including special keys like SHIFT or CTRL etc.

But isn't that a feature that the OS implements?

I mean if you were to implement the automatically repeated keypresses on the ESP, then the related settings in Windows, Linux etc would become useless.


Yes, i think you are correct, my bad.

I programmed ESP like this: by pressing the "right arrow" key (example), ESP sends "bleKeyboard.write (KEY_RIGHT_ARROW)". After 500ms if the key remains pressed, ESP sends "KEY_RIGHT_ARROW" repeatedly until you release the key. I am trying to mimic the behavior of a conventional keyboard. However, I noticed that each "KEY_RIGHT_ARROW" sends a command package "keydown + key value + keyup". A conventional keyboard, in addition to the instruction of that key, sends the state of the key, ie when it was pressed and when it was released. T-vK sends it all at once. I don't know if I'm being clear. Sorry, I'm using a translator.

Just like in the example “SendKeyStrokes.ino” the command “bleKeyboard.releaseAll()” is sent to release all keys, so I need to send the commands “bleKeyboard.press(KEY_RIGHT_ARROW)” and “bleKeyboard.release(KEY_RIGHT_ARROW)” ? Oops, I think I was messing up. Should key "KEY_RIGHT_ARROW" be sent by "bleKeyboard.press()"? I was using "bleKeyboard.write()". Looking at "BleKeyboard.cpp" the "write" command sends "keydown/keyup" together. So that was the problem. I'm sorry guys, it was my fault. I will modify the code in ESP and do the tests.

Yes, if you want to control when a given key is released then you must not use bleKeyboard.write(...), but bleKeyboard.press(...) and either bleKeyboard.release(...) or bleKeyboard.releaseAll(...).

If you want to mimic a real keyboard then you don't implement the repeated keypresses on the ESP because as I already said in the previous post, this is a feature that is meant to be controlled by the target device.

Running perfectly now. Thank you all for the clarifications.

Hi, I'm really knew to Arduino and C++.

I think I'm at the right place, I'm trying to make a page turner for an Ipad app, and so far, this is the thread that got me the furthest to reach my goal.

I'm sorry in advance if it's supposed to be "so simple", but how and where in the code (thank you @chegewara ) am I supposed to declare the keyboard character ? And if I want to link the keyboard character to an event (pressing a button, indeed), how would I do so ?

Thank you guys !

Look at the examples in the IDE. There are examples for buttons and examples for this ble keyboard library. You really just need to merge them and use common sense.

Wow, thanks so much ! It's almost done, and I basically just replaced some lines on the example.

Am looking to modify @T-vK mouse library to be an absolute mouse so I can use it for a wireless IR lightgun, similar to the SAMCO project https://github.com/samuelballantyne/IR-Light-Gun/tree/master/Samco_2.0 where they use the AbsMouse library.

I've got it working, where I can pass the modified library the X,Y coordinates I'd like it to go to, and it goes really well.

One limitation is that you have to pass in the screen resolution when starting the library. This can be a hassle where you want to use your lightgun in many different emulator and the resolution may change from game to game,

Question: Is there a way to have the ESP query what resolution Windows 10 is running, then pass that information back to the ESP over BLE so the library can use the current resolution?

I guess I could make a calibration button to throw it into regular BT serial mode, make a C# app that passes the values to the ESP, save them to SPIFF, and then reboot to use the values.

Sending the values to the ESP without a calibration app would be heaps better though

I would have to dig some more in HID specification, i dont remember now if its possible.
I know i did help very log time ago with some mouse like device:
https://github.com/markingle/Assist_IoT_ESP32_LipSync
but dont remember how it was handled in there. You can try to read code.

I don't think that's possible without your own driver/script/app on Windows. Be aware that the screen resolution can change at any time and you get pretty interesting results for multimonitor setups, especially if they run different resolutions on each monitor or their virtual placement is customized.
It should be possible to create an hid descriptor that combines serial and mouse into one device. This way you could just send the data during runtime.
Another challenge I can see are applications that take over your entire display output, like directx fullscreen etc. I don't think the WinAPI will be able to tell you the resolution in those cases and reading it from the processes memory could get you banned if some sort of anticheat mechanisms are in place.

Worst case scenario you can have button/buttons to select resolution from most common.

Thanks for the well reasoned replies.

I would be very interested to see how to incorporate serial communication as a composite device.
I will probably be using this mouse/keyboard combo library
https://github.com/blackketter/ESP32-BLE-Combo
which is by @blackletter based on @T-vK 's work to facilitate the mouse moves (will change the mouse to absolute) and also a number of keyboard keys. I wonder how hard it would be to add. Are there any examples?

At the end of the day though, maybe a button to select resolutions on the device is the way to go.
Might also give me a reason to pop an SSD1306 OLED in there for other options too to see some type of feedback.

I do like problem solving though and I can't help thinking that adding serial on top of BLE mouse/keyboard would be super great and might come in useful for others.

So, any idea if it's possible to run serial (where a COM port number is allocated and data may be sent at say 115200 or 250000 baud) at the same time as BLE keyboard/mouse?

Sure it is possible.
Regular debug log is running on serial (UART 0). But you will need ftdi uart to usb to connect with PC.

Hi

Thanks for the reply
Sorry - I meant both over bluetooth

Hi

Thanks for the reply
Sorry - I meant both over bluetooth

In theory it should be possible, but i didnt work with classic bluetooth, so i cant help with that.

I didn't even think to try running Classic BT at the same time as BLE. I just assumed you'd need to add some type of BLE COM port wizardry.

I threw together a sketch with both of them and it compiles.
I'll test it later on and reply with how it goes.

Thanks!

Hi!
Does anyone have problems with the mouse cursor not moving smoothly?
I'm trying to run code from https://github.com/T-vK/ESP32-BLE-Mouse in the simplest way possible, that means repeating move(1,1,0) every 20 ms(tried with various numbers).
Mouse doesn't move nicely one pixel down and one left(it shall have 50 fps) instead of this it jumps few at once and it's very clear. Is esp not robust enough? shall I do it some other way? BLE frames are being quequed or what? Can anyone tell me? Maybe i shall write something more about what and how i do? I use WROOM32U without devboard(tho i guess it doesn't matter). Maybe you can tell me about some other uC that would perform in this task better?
This time i did it simplest possible, but i had the same problem in my previous project with some code provided by @chegewara (btw thank you dude for your awesome work in this field. You and NKolban are awesome! :D Pozdrawiam!).
I've already worked on the subject a year ago (or so) but dumped it due to the lack of time and this issue...

Greetings again!

PS. It's important for me because I'll use this device for gaming... when mice works this way you may get a headache during rotating view :D It's not so visible while using it in perfect conditions but very visible when i compare my bluetooth mice(real) and this soltion

PSS. Ive made some wireshark sniffing and every move() frame sent by esp is 16 bytes long.
with 50Hz it gives 800 B/s.
Here (https://esp32.com/viewtopic.php?t=2262) some guy writes about 12kB(after some fight to squeeze as much as possible) so i assume that the bandwidth is not a problem.
ESP isn't also too slow because it sends 50 frames like this is 11 ms(measured by looping it in for 50 times and printing difference between millis() results)
Ive also measured how long sending move() then waiting 20ms(delay) will take while doing it that 50 times(it shall be 1011 but surprisingly i got equal 1000)
Can it be improved by using normal Bluetooth instead of BLE? Maybe the connection and data loss is the problem?
So whats wrong then....

Hi,
my advice is to send data faster, esp32 should handle about 100Hz with BLE (with good conditions maybe up to 200Hz).

Everything depends how you build mouse from HID point of view. Usually mouse is using relative data, which means you are sending how far move pointer from current position. Then, after sending that you probably want to send value 0x0 or pointer will continue moving.

If you are asking about other uC to make this project i would rather suggest to try nrf52840, 53811, 52833. NRF chips are specialized BLE chips with very good power consumption ratio. esp32 is too powerful to only build mouse. Problem with nrf is that you have to learn new RTOS, because of lack good arduino support.

Thank you for your answer! :-) Latest tests ive made by arduino IDE and the oscillator was set on 240Mhz. Unless you mean the internal BLE transmission rate (dunno how i can set it up tho tomorrow I'll check all the documentation. Maybe it can help really.)

And about sending the stop value... well... you really made me think where's the mistake in the code. I remember that at first try ive took your keyboard example and modified it to be usable as a mouse, tho it didn't require a stop val. It was just moving cursor the given pixels in y and x once. If it's really supposed to work as you say then it's much better and the mistake is just a matter of a bug in my code. It casts new light on the case :D.

Thanks for the chip proposition also. Now i have plan B if esp wont work.
By the way project is a bit more complicated than a mouse. It's supposed to integrate both mouse and a keyboard in a single one-handed device, and it does require some computation power indeed. Maybe after some time i'll share somewhere the results :-)

Thanks again!

To send stop moving you just have to send X = 0x0 and Y = 0x0, just like in keyboard you have to send key = 0x0 or it will be repeated by system. I think its the same way in case of mouse, but its a small chance and you dont have to send 0x0 for mouse. To make sure you can try to send X = 100 just 1 time and see if mouse pointer will move only 1 time or more.

nrf chips are enough powerful to handle keyboard + mouse + even more, especially nrf52840.

@lemmingDev :

I've got it working, where I can pass the modified library the X,Y coordinates I'd like it to go to, and it goes really well.
This is great ! Did you get it to work with Android on absolute mode on landscape orientation ?

Question: Is there a way to have the ESP query what resolution Windows 10 is running, then pass that information back to the ESP over BLE so the library can use the current resolution?
Yes use the SPP chanel for that purpose (even if it only once).

Sending the values to the ESP without a calibration app would be heaps better though
The HID ble channel at handshake transaction with the host sending back the resolution to the peripheral could be great.

Hi All

Any idea how to get Windows Game Controller panel to list the actual device name?

In my library https://github.com/lemmingDev/ESP32-BLE-Gamepad the name is set and comes up correctly in device manager and Bluetooth settings
image
image

In the game controller panel however, it just lists the number of axis and buttons as the name.
What's worse, is that it lists them for the first time the BLE gamepad was connected. For example, during development of the library, I started with 11 buttons, and now I'm at 32 buttons, but it still lists it as 11.

image

Hoping someone may be able to point me in the right direction

On a perhaps related note:

It should be noted that I edited the the https://github.com/T-vK/ESP32-BLE-Mouse library in order to make this library, so I have a feeling some values could be set wrong too.

I know my usage is set correctly in the HID report descriptor

USAGE(1),            0x05, // USAGE (Gamepad)

however I'm not sure about the following which were blindly copied

  BleGamepadInstance->hid->pnp(0x01,0x02e5,0xabcd,0x0110);
  BleGamepadInstance->hid->hidInfo(0x00,0x01);

If I change the pnp hex values above slightly: eg 0xabcd to 0xabcc so it thinks it's a new device, then Windows correctly changes the amount of buttons to:
image
however I'd still like it so say the actual name

Was this page helpful?
0 / 5 - 0 ratings

Related issues

frankipl picture frankipl  ·  8Comments

hansmbakker picture hansmbakker  ·  6Comments

JasXSL picture JasXSL  ·  3Comments

Lakoja picture Lakoja  ·  8Comments

mkol5222 picture mkol5222  ·  5Comments