Last active
January 29, 2023 17:48
-
-
Save djavorek/bbab52e010818f8e5717b881217f1903 to your computer and use it in GitHub Desktop.
ESP32 - Automated Bluetooth keyboard, waiting for trigger on WiFi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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