Skip to content

Instantly share code, notes, and snippets.

@djavorek
Last active January 29, 2023 17:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djavorek/bbab52e010818f8e5717b881217f1903 to your computer and use it in GitHub Desktop.
Save djavorek/bbab52e010818f8e5717b881217f1903 to your computer and use it in GitHub Desktop.
ESP32 - Automated Bluetooth keyboard, waiting for trigger on WiFi
/*
ESP32 program that emulates a Blutooth keyboard and types a predefined message,
when either the specified button was pressed or any message was sent through WiFi.
NOTE: Does not fit to default ESP32 build partition (as flash size is too big)
Requires "board_build.partitions = huge_app.csv" in PlatformIO
(or corresponding configuration in Arduino)
Copyright (c) 2023 Denes Javorek
Copyright (c) 2019 Manuel Bl
Licensed under MIT License
https://opensource.org/licenses/MIT
*/
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"
#include "WiFi.h"
#define uS_TO_S_FACTOR 1000000ULL
#define BUTTON_PIN 0
#define BLE_DEVICE_NAME "ESP32 Keyboard"
#define MESSAGE_TO_TYPE "Hello from ESP32"
#define WIFI_SSID "YourWifiNetwork"
#define WIFI_PASSWORD "YourWifiPassword"
// Forward declarations
void handleHttpRequest(WiFiClient client);
void typeText(const char *text);
void connectToWifi(const char *ssid, const char *password, int maxRetries);
void bluetoothTask(void *);
// 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
};
const InputReport NO_KEY_PRESSED = {};
// 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;
bool isBluetoothClientConnected = false;
WiFiServer server(80);
void setup()
{
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
connectToWifi(WIFI_SSID, WIFI_PASSWORD, 5);
xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL);
}
void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
connectToWifi(WIFI_SSID, WIFI_PASSWORD, 5);
}
WiFiClient client = server.available();
if (digitalRead(BUTTON_PIN) == LOW)
{
Serial.print("Button was pressed, typing the message: ");
Serial.println(MESSAGE_TO_TYPE);
typeText(MESSAGE_TO_TYPE);
}
else if (client && client.available())
{
handleHttpRequest(client);
}
delay(200);
}
void handleHttpRequest(WiFiClient client)
{
if (client)
{
String currentLine = "";
while (client.connected())
{
if (client.available())
{
char c = client.read();
// Serial.write(c); // Logging request data
if (c == '\n')
{
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0)
{
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.println("<h3>Serving your automated typing needs.</h3>");
client.println();
break;
}
else
{
currentLine = "";
}
}
else if (c != '\r')
{
currentLine += c;
}
if (currentLine.endsWith("GET /type"))
{
Serial.print("Incoming type request, typing the message: ");
Serial.println(MESSAGE_TO_TYPE);
typeText(MESSAGE_TO_TYPE);
}
}
}
client.stop();
}
}
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];
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(150);
// 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(50);
}
}
void connectToWifi(const char *ssid, const char *password, int maxRetries)
{
while (WiFi.status() != WL_CONNECTED)
{
Serial.print("Connecting to WiFi");
WiFi.begin(ssid, password);
int retries = 0;
do
{
Serial.print(".");
delay(500);
retries++;
} while (WiFi.status() != WL_CONNECTED && retries <= maxRetries);
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("Could not connect to WiFi, check your network and credentials.");
Serial.println("Sleeping before trying connect again..");
delay(30000);
}
}
Serial.print("Connected, with IP: ");
Serial.println(WiFi.localIP());
server.begin();
}
class BleKeyboardCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *server)
{
isBluetoothClientConnected = true;
// Allow notifications for characteristics
BLE2902 *cccDesc = (BLE2902 *)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
cccDesc->setNotifications(true);
Serial.println("BLE client has connected.");
}
void onDisconnect(BLEServer *server)
{
isBluetoothClientConnected = false;
// Disallow notifications for characteristics
BLE2902 *cccDesc = (BLE2902 *)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
cccDesc->setNotifications(false);
server->getAdvertising()->start();
Serial.println("BLE client has disconnected.");
}
};
void bluetoothTask(void *)
{
// Initialize the device
BLEDevice::init(BLE_DEVICE_NAME);
BLEServer *server = BLEDevice::createServer();
server->setCallbacks(new BleKeyboardCallbacks());
// Create an HID device
hid = new BLEHIDDevice(server);
input = hid->inputReport(1);
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);
hid->reportMap((uint8_t *)REPORT_MAP, sizeof(REPORT_MAP));
hid->startServices();
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 is ready to receive clients");
delay(portMAX_DELAY);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment