Skip to content

Instantly share code, notes, and snippets.

@manuelbl
Created August 3, 2019 09:12
Show Gist options
  • Save manuelbl/66f059effc8a7be148adb1f104666467 to your computer and use it in GitHub Desktop.
Save manuelbl/66f059effc8a7be148adb1f104666467 to your computer and use it in GitHub Desktop.
ESP32 as Bluetooth Keyboard

ESP32 as Bluetooth Keyboard

With its built-in Bluetooth capabilities, the ESP32 can act as a Bluetooth keyboard. The below code is a minimal example of how to achieve it. It will generate the key strokes for a message whenever a button attached to the ESP32 is pressed.

For the example setup, a momentary button should be connected to pin 2 and to ground. Pin 2 will be configured as an input with pull-up.

In order to receive the message, add the ESP32 as a Bluetooth keyboard of your computer or mobile phone:

  1. Go to your computers/phones settings
  2. Ensure Bluetooth is turned on
  3. Scan for Bluetooth devices
  4. Connect to the device called "ESP32 Keyboard"
  5. Open an empty document in a text editor
  6. Press the button attached to the ESP32

The code has been written for the Arduino framework. I recommend using PlatformIO for development as it is far superior to the Arduino IDE while still taking full advantage of the Arduino ecosystem (libraries, support etc.)

/*
* Sample program for ESP32 acting as a Bluetooth keyboard
*
* Copyright (c) 2019 Manuel Bl
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*/
//
// This program lets an ESP32 act as a keyboard connected via Bluetooth.
// When a button attached to the ESP32 is pressed, it will generate the key strokes for a message.
//
// For the setup, a momentary button should be connected to pin 2 and to ground.
// Pin 2 will be configured as an input with pull-up.
//
// In order to receive the message, add the ESP32 as a Bluetooth keyboard of your computer
// or mobile phone:
//
// 1. Go to your computers/phones settings
// 2. Ensure Bluetooth is turned on
// 3. Scan for Bluetooth devices
// 4. Connect to the device called "ESP32 Keyboard"
// 5. Open an empty document in a text editor
// 6. Press the button attached to the ESP32
#define US_KEYBOARD 1
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"
// Change the below values if desired
#define BUTTON_PIN 2
#define MESSAGE "Hello from ESP32\n"
#define DEVICE_NAME "ESP32 Keyboard"
// Forward declarations
void bluetoothTask(void*);
void typeText(const char* text);
bool isBleConnected = false;
void setup() {
Serial.begin(115200);
// configure pin for button
pinMode(BUTTON_PIN, INPUT_PULLUP);
// start Bluetooth task
xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL);
}
void loop() {
if (isBleConnected && digitalRead(BUTTON_PIN) == LOW) {
// button has been pressed: type message
Serial.println(MESSAGE);
typeText(MESSAGE);
}
delay(100);
}
// Message (report) sent when a key is pressed or released
struct InputReport {
uint8_t modifiers; // bitmask: CTRL = 1, SHIFT = 2, ALT = 4
uint8_t reserved; // must be 0
uint8_t pressedKeys[6]; // up to six concurrenlty pressed keys
};
// Message (report) received when an LED's state changed
struct OutputReport {
uint8_t leds; // bitmask: num lock = 1, caps lock = 2, scroll lock = 4, compose = 8, kana = 16
};
// The report map describes the HID device (a keyboard in this case) and
// the messages (reports in HID terms) sent and received.
static const uint8_t REPORT_MAP[] = {
USAGE_PAGE(1), 0x01, // Generic Desktop Controls
USAGE(1), 0x06, // Keyboard
COLLECTION(1), 0x01, // Application
REPORT_ID(1), 0x01, // Report ID (1)
USAGE_PAGE(1), 0x07, // Keyboard/Keypad
USAGE_MINIMUM(1), 0xE0, // Keyboard Left Control
USAGE_MAXIMUM(1), 0xE7, // Keyboard Right Control
LOGICAL_MINIMUM(1), 0x00, // Each bit is either 0 or 1
LOGICAL_MAXIMUM(1), 0x01,
REPORT_COUNT(1), 0x08, // 8 bits for the modifier keys
REPORT_SIZE(1), 0x01,
HIDINPUT(1), 0x02, // Data, Var, Abs
REPORT_COUNT(1), 0x01, // 1 byte (unused)
REPORT_SIZE(1), 0x08,
HIDINPUT(1), 0x01, // Const, Array, Abs
REPORT_COUNT(1), 0x06, // 6 bytes (for up to 6 concurrently pressed 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
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
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0x01,
HIDOUTPUT(1), 0x02, // Data, Var, Abs
REPORT_COUNT(1), 0x01, // 3 bits (Padding)
REPORT_SIZE(1), 0x03,
HIDOUTPUT(1), 0x01, // Const, Array, Abs
END_COLLECTION(0) // End application collection
};
BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;
const InputReport NO_KEY_PRESSED = { };
/*
* Callbacks related to BLE connection
*/
class BleKeyboardCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* server) {
isBleConnected = true;
// Allow notifications for characteristics
BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
cccDesc->setNotifications(true);
Serial.println("Client has connected");
}
void onDisconnect(BLEServer* server) {
isBleConnected = false;
// Disallow notifications for characteristics
BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
cccDesc->setNotifications(false);
Serial.println("Client has disconnected");
}
};
/*
* Called when the client (computer, smart phone) wants to turn on or off
* the LEDs in the keyboard.
*
* bit 0 - NUM LOCK
* bit 1 - CAPS LOCK
* bit 2 - SCROLL LOCK
*/
class OutputCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* characteristic) {
OutputReport* report = (OutputReport*) characteristic->getData();
Serial.print("LED state: ");
Serial.print((int) report->leds);
Serial.println();
}
};
void bluetoothTask(void*) {
// initialize the device
BLEDevice::init(DEVICE_NAME);
BLEServer* server = BLEDevice::createServer();
server->setCallbacks(new BleKeyboardCallbacks());
// create an HID device
hid = new BLEHIDDevice(server);
input = hid->inputReport(1); // report ID
output = hid->outputReport(1); // report ID
output->setCallbacks(new OutputCallbacks());
// set manufacturer name
hid->manufacturer()->setValue("Maker Community");
// set USB vendor and product ID
hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
// information about HID device: device is not localized, device can be connected
hid->hidInfo(0x00, 0x02);
// Security: device requires bonding
BLESecurity* security = new BLESecurity();
security->setAuthenticationMode(ESP_LE_AUTH_BOND);
// set report map
hid->reportMap((uint8_t*)REPORT_MAP, sizeof(REPORT_MAP));
hid->startServices();
// set battery level to 100%
hid->setBatteryLevel(100);
// advertise the services
BLEAdvertising* advertising = server->getAdvertising();
advertising->setAppearance(HID_KEYBOARD);
advertising->addServiceUUID(hid->hidService()->getUUID());
advertising->addServiceUUID(hid->deviceInfo()->getUUID());
advertising->addServiceUUID(hid->batteryService()->getUUID());
advertising->start();
Serial.println("BLE ready");
delay(portMAX_DELAY);
};
void typeText(const char* text) {
int len = strlen(text);
for (int i = 0; i < len; i++) {
// translate character to key combination
uint8_t val = (uint8_t)text[i];
if (val > KEYMAP_SIZE)
continue; // character not available on keyboard - skip
KEYMAP map = keymap[val];
// create input report
InputReport report = {
.modifiers = map.modifier,
.reserved = 0,
.pressedKeys = {
map.usage,
0, 0, 0, 0, 0
}
};
// send the input report
input->setValue((uint8_t*)&report, sizeof(report));
input->notify();
delay(5);
// release all keys between two characters; otherwise two identical
// consecutive characters are treated as just one key press
input->setValue((uint8_t*)&NO_KEY_PRESSED, sizeof(NO_KEY_PRESSED));
input->notify();
delay(5);
}
}
@legitlee
Copy link

legitlee commented Oct 15, 2023 via email

@simplyrohan
Copy link

Are there any parts I can cut out to make the program size smaller? I don't have a lot of memory, and this uses up a lot of it. I don't need any special characters, numbers, or control keys. Just the basic, English, character set. I have a rough idea of how to do this, but I'm not sure... Thanks for any help, and this is a great example code!

@Dreamcazman
Copy link

Dreamcazman commented Dec 3, 2023

Are there any parts I can cut out to make the program size smaller? I don't have a lot of memory, and this uses up a lot of it. I don't need any special characters, numbers, or control keys. Just the basic, English, character set. I have a rough idea of how to do this, but I'm not sure... Thanks for any help, and this is a great example code!

I'm not sure, it's already fairly compact as it uses Nimble. You may be able to reduce the number of inputs or the debounce code could be removed but without it can cause phantom or unwanted button presses.

Other than that, maybe invest in an Arduino device with more available memory.

@simplyrohan
Copy link

simplyrohan commented Dec 3, 2023

You may be able to reduce the number of inputs

Any idea how I would do this? I see the InputReport struct and REPORT_MAP array, but I'm not sure what I would need to change...

@Dreamcazman
Copy link

Any idea how I would do this? I see the InputReport struct and REPORT_MAP array, but I'm not sure what I would need to change...

For example, if you want to remove the 'Spacebar' input, remove the following:

'#define Spacebar 2' and 'pinMode(Spacebar, INPUT_PULLUP);' lines

This part, remove one of the 'false' entries so there are only eight left, then remove 'Spacebar', it's corresponding key code and change the number from 9 to 8, so it looks like this:

bool keyStates[8] = {false, false, false, false, false, false, false, false};
int keyPins[8] = {Up, Down, Left, Right, Coin, Start, Enter, Escape};
uint8_t keyCodes[8] = {(218), (217), (216), (215), '5', '1', (176), (177)};

Then any other place in the code where it mentions '9' (like above), change it to how many inputs there are, ie. it's now 8.

Hope that makes sense.

@legitlee
Copy link

legitlee commented Dec 3, 2023 via email

@manuelbl
Copy link
Author

manuelbl commented Dec 3, 2023

@simplyrohan The code on this page is quite small. I doubt that any optimizations will make a relevant difference.

The major contributor to the code and RAM size is Bluetooth itself. It's a non-trivial protocol. The moment a single Bluetooth function is used, a large library is pulled in.

So if you need to shrink the code, focus on the Bluetooth library:

  • ESP-NimBLE is supposedly smaller than ESP-Bluedroid
  • Test if certain configuration options reduce flash and memory size, e.g. by turning of unnecessary features (e.g. no central role, reduced logging to get rid of logging messages in the code)

@sundarv74
Copy link

Hello,
Can we connect to two Android phones at the same time and send keys to both from the ESP32? May I request what modifications are needed and any possible code sample for the purpose?
Regards, Sundar.

@TechRelief
Copy link

Trying to make it work with an Espressif ESP32-S3-USB-OTG board it connects to bluetooth (windows 10) and responds when a button is pressed on the board and reaches the typeText() function which does its thing, but there is no ouput at all.
I put a Serial.print() statement to monitor what it is trying to output and for a typeText("abc") it outputs "456", but I am confused about the fact it has references to input->setValue should it not Output? Any thoughts?
Also where is the best way to read up on what it is actually trying to send (i.e. via bluetooth so I can perhaps try to debug this?

@TechRelief
Copy link

Ok so I seem to finally have it working; but I do not know why exactly but I did have to create a new board definition for ESP32-S3-USB-OTG and PlatformIO does not publish what the settings in the board JSON file are for, they tell you to look at existing board definitions for clues. So I copied some settings from other ESP32-S3 boards and somehow it started to work. I do have a question though:
At the end of the bluetoothTask() function it has: delay(portMAX_DELAY);
How does this work exactly? portMAX_DELAY is an unsigned 0xFFFFFFFF value which equates to a delay of 49 days in milliseconds? Is this a kludge to halt code execution here? If anyone knows please enlighten me, thanks...

@wvsbsp
Copy link

wvsbsp commented Dec 17, 2023

Works like a charm! Many thanks! I've been watching this project for a while.
Now i need a bit of hardware to scroll in a PDF file while playing an instrument. I needed only 3 control-keys KEY_PAGE_UP, KEY_PAGE_DOWN and KEY_F11. The longest it took me to find the names for the control keys. It would be a good idea to insert a link in the comment to
~\Arduino15\packages\esp32\hardware\esp32\2.0.14\libraries\BLE\src\HIDKeyboardTypes.h
That's all i had to program: ;-)
`bool bTaster0, bTaster2, bTaster4, oldTaster0, oldTaster2, oldTaster4;
void loop() {
bTaster0 = digitalRead(Taster0);
bTaster2 = digitalRead(Taster2);
bTaster4 = digitalRead(Taster4);

if (isBleConnected && bTaster2 == false && oldTaster2 == true) {
// button has been pressed: type message
//Serial.println(MESSAGE);
//typeText(MESSAGE);
char NewMessage[] = { KEY_PAGE_UP, 0 };
typeText(NewMessage);
}
if (isBleConnected && bTaster4 == false && oldTaster4 == true) {
// button has been pressed: type message
char NewMessage[] = { KEY_PAGE_DOWN, 0 };
typeText(NewMessage);
}
if (isBleConnected && bTaster0 == false && oldTaster0 == true) {
// button has been pressed: type message
char NewMessage[] = { KEY_F11, 0 };
typeText(NewMessage);
}
oldTaster0 = bTaster0;
oldTaster2 = bTaster2;
oldTaster4 = bTaster4;

delay(100);
}`

@HectorFraguas
Copy link

I'm trying to connect it with an Android device and it connects correctly but I can't get the volume up and volume down keys to work, has anyone got something like that?

@arvebjoe
Copy link

arvebjoe commented Mar 5, 2024

I can't get the media play/pause keys to work. Other keys like arrow up or down work fine. I've tried 0x48 (KEY_PAUSE) and 0xB3 (after searching online). Any suggestions?

Example:
sendKeyCode(KEY_PAUSE); // doesn't work
sendKeyCode(0xB3); // doesn't work
sendKeyCode(KEY_DOWN); // works fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment