diff --git a/README.md b/README.md index bb34336..22cd2ed 100644 --- a/README.md +++ b/README.md @@ -1 +1,76 @@ # Mon Key +# BLE Keyboard + +## **Description** +This project turns an `ESP32` into a `Bluetooth keyboard`. When you connect your computer to the ESP32 via Bluetooth, any key you press on your keyboard is sent to the ESP32, which then transmits it back with a Bluetooth communication to reproduce a real `wireless keyboard`. + +To make it work, you need to: +1. Pair your ESP32 with your computer via Bluetooth +2. Start the Python script on your PC, which listens for key event +3. Open the Serial Monitor in Arduino IDE (this seems to be necessary for the communication between the ESP32 and the computer. without it, the ESP32 might not receive the keystrokes) + +Once everything is set up, each key you type on your computer is forwarded to the ESP32 over serial, and the ESP32 then sends it as a Bluetooth HID input. This effectively makes your ESP32 act as a wireless keyboard. + +## **Depedencies** +You need to use python3 and .ino code wich python will be a deamon script on your PC and .ino will be flashed in the ESP32. + +### **Venv** +If you are in `Arch` or other distrib that don't accept pip install directly you will need venv to run the following, otherwise you don't need to use it. The installation with Arch is : +``` +sudo pacman -S python-venv +``` + +Place the venv environment in `esp32` so go one file before it with +``` +cd .. +``` + +And run what's below to install and start the venv environment +``` +python3 -m venv esp32/ +source esp32/bin/activate +``` +Now your venv environment is setup. + +### **Python Dependencies** +To install the required Python dependencies, if not go to `esp32/BLE_Keyboard/` file with +``` +cd esp32/BLE_Keyboard/ +``` + +And then simply run +``` +pip install -r requirements.txt +``` + +### **Arduino Libraries** +For the ESP32 firmware, you need to install some Arduino libraries. There are two ways to do this, depending on your setup : + +**Using Arduino IDE** +1. Open `Arduino IDE` +2. Go to `Sketch > Include Library > Manage Libraries` +3. Search for the required libraries wich is `Arduino.h` +4. And install it + +**Using PlatformIO** +1. Open your project in VS Code with PlatformIO installed +2. Open `platformio.ini` and add the necessary libraries under the `[env]` section +3. If not go to `esp32/` file and Run +``` +pio lib install +``` + +## **Launch** +1. Flash the esp32 with `BLE_Keyboard.ino` with Arduino-IDE or PlatformIO +2. When the ESP32 is flashed a Bluetooth access point will apear. Connect your computer to it +3. Start the python deamon program with +- Arch (or using venv) +``` +sudo env "PATH=$PATH" python3 BLE_Keyboard/Send_keystroke_ESP32.py +``` +- Other accepting pip +``` +sudo python3 BLE_Keyboard/Send_keystroke_ESP32.py +``` +4. Here the Bluetooth keyboard should not be working (it wase the case for me) so you can open the `serial monitor` with PlatformIO or Arduino-IDE +Here you can leave these windows in background, go to text input and you should see that every key pressed, it will wait a bit a pressed it again without doing anything. diff --git a/esp32/BLE_Keyboard/BLE_Keyboard.ino b/esp32/BLE_Keyboard/BLE_Keyboard.ino new file mode 100644 index 0000000..c372a4a --- /dev/null +++ b/esp32/BLE_Keyboard/BLE_Keyboard.ino @@ -0,0 +1,346 @@ +/* + * 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 +#include "BLEDevice.h" +#include "BLEHIDDevice.h" +#include "HIDTypes.h" +#include "HIDKeyboardTypes.h" + + +// Change the below values if desired +#define DEVICE_NAME "ESP32 Keyboard" + + +// Forward declarations +void bluetoothTask(void*); +void typeText(const char* text); + +String MESSAGE; + +bool isBleConnected = false; + + +void setup() { + Serial.begin(115200); + + // start Bluetooth task + xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL); +} + + +void loop() { + if (isBleConnected) { + + while (Serial.available()) { + delay(3); + if (Serial.available() >0) { + char c = Serial.read(); + MESSAGE += c; + } + Serial.print("Message reçu : "); + Serial.println(MESSAGE); + } + String messageConverted = azertyToQwerty(MESSAGE.c_str()); + Serial.println(messageConverted); + typeText(messageConverted.c_str()); + 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); + } +} + +String azertyToQwerty(const char* text) { + String converted = ""; + while (*text) { + switch (*text) { + case 'a': converted += 'q'; break; + case 'z': converted += 'w'; break; + case 'e': converted += 'e'; break; + case 'r': converted += 'r'; break; + case 't': converted += 't'; break; + case 'y': converted += 'y'; break; + case 'u': converted += 'u'; break; + case 'i': converted += 'i'; break; + case 'o': converted += 'o'; break; + case 'p': converted += 'p'; break; + case 'q': converted += 'a'; break; + case 's': converted += 's'; break; + case 'd': converted += 'd'; break; + case 'f': converted += 'f'; break; + case 'g': converted += 'g'; break; + case 'h': converted += 'h'; break; + case 'j': converted += 'j'; break; + case 'k': converted += 'k'; break; + case 'l': converted += 'l'; break; + case 'm': converted += 'm'; break; + case 'w': converted += 'z'; break; + case 'x': converted += 'x'; break; + case 'c': converted += 'c'; break; + case 'v': converted += 'v'; break; + case 'b': converted += 'b'; break; + case 'n': converted += 'n'; break; + case ',': converted += ';'; break; + case ';': converted += 'm'; break; + case ':': converted += '.'; break; + case '!': converted += '!'; break; + case 'A': converted += 'Q'; break; + case 'Z': converted += 'W'; break; + case 'E': converted += 'E'; break; + case 'R': converted += 'R'; break; + case 'T': converted += 'T'; break; + case 'Y': converted += 'Y'; break; + case 'U': converted += 'U'; break; + case 'I': converted += 'I'; break; + case 'O': converted += 'O'; break; + case 'P': converted += 'P'; break; + case 'Q': converted += 'A'; break; + case 'S': converted += 'S'; break; + case 'D': converted += 'D'; break; + case 'F': converted += 'F'; break; + case 'G': converted += 'G'; break; + case 'H': converted += 'H'; break; + case 'J': converted += 'J'; break; + case 'K': converted += 'K'; break; + case 'L': converted += 'L'; break; + case 'M': converted += 'M'; break; + case 'W': converted += 'Z'; break; + case 'X': converted += 'X'; break; + case 'C': converted += 'C'; break; + case 'V': converted += 'V'; break; + case 'B': converted += 'B'; break; + case 'N': converted += 'N'; break; + case '?': converted += '/'; break; + case '.': converted += '>'; break; + case '/': converted += '?'; break; + case '&': converted += '1'; break; + case 'é': converted += '2'; break; + case '"': converted += '3'; break; + case '\'': converted += '4'; break; + case '(': converted += '5'; break; + case '-': converted += '6'; break; + case 'è': converted += '7'; break; + case '_': converted += '8'; break; + case 'ç': converted += '9'; break; + case 'à': converted += '0'; break; + case ')': converted += '-'; break; + case '=': converted += '='; break; + default: converted += *text; break; + } + text++; + } + return converted; +} diff --git a/esp32/BLE_Keyboard/Send_keystroke_ESP32.py b/esp32/BLE_Keyboard/Send_keystroke_ESP32.py new file mode 100644 index 0000000..4138635 --- /dev/null +++ b/esp32/BLE_Keyboard/Send_keystroke_ESP32.py @@ -0,0 +1,54 @@ +import keyboard +import serial +import time + +print("Appuie sur une touche (CTRL+C pour arrêter)") + +ser = serial.Serial( + port='/dev/ttyUSB0', + baudrate=9600, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS +) + +key_map = { + "enter": "\n", # Touche Entrée + "backspace": "\b", # Retour arrière + "space": " ", # Espace + "tab": "\t", # Tabulation + "esc": "\x1b", # Échappement + "delete": "\x7f", # Suppr + + # Chiffres et symboles + "1": "1", "2": "2", "3": "3", "4": "4", "5": "5", "6": "6", "7": "7", "8": "8", "9": "9", "0": "0", + "!": "!", "@": "@", "#": "#", "$": "$", "%": "%", "^": "^", "&": "&", "*": "*", "(": "(", ")": ")", + "-": "-", "_": "_", "=": "=", "+": "+", "[": "[", "]": "]", "{": "{", "}": "}", "\\": "\\", "|": "|", + ";": ";", ":": ":", "'": "'", "\"": "\"", ",": ",", "<": "<", ".": ".", ">": ">", "/": "/", "?": "?", + + # Lettres (min et maj) + "a": "a", "b": "b", "c": "c", "d": "d", "e": "e", "f": "f", "g": "g", "h": "h", "i": "i", "j": "j", + "k": "k", "l": "l", "m": "m", "n": "n", "o": "o", "p": "p", "q": "q", "r": "r", "s": "s", "t": "t", + "u": "u", "v": "v", "w": "w", "x": "x", "y": "y", "z": "z", + "A": "A", "B": "B", "C": "C", "D": "D", "E": "E", "F": "F", "G": "G", "H": "H", "I": "I", "J": "J", + "K": "K", "L": "L", "M": "M", "N": "N", "O": "O", "P": "P", "Q": "Q", "R": "R", "S": "S", "T": "T", + "U": "U", "V": "V", "W": "W", "X": "X", "Y": "Y", "Z": "Z", + + # Touches de fonction + "f1": "\x3a", "f2": "\x3b", "f3": "\x3c", "f4": "\x3d", "f5": "\x3e", "f6": "\x3f", "f7": "\x40", + "f8": "\x41", "f9": "\x42", "f10": "\x43", "f11": "\x44", "f12": "\x45", + + # Autres touches spéciales + "caps lock": "\x39", "num lock": "\x53", "scroll lock": "\x47", "insert": "\x49", "home": "\x4a", + "page up": "\x4b", "page down": "\x4e", "print screen": "\x46", + + # Touches fléchées + "left": "\x50", "right": "\x4f", "up": "\x52", "down": "\x51" +} + +while True: + event = keyboard.read_event() + if event.event_type == keyboard.KEY_UP: + key_to_send = key_map.get(event.name, event.name) + ser.write(key_to_send.encode()) + print(f"Touche envoyée : {key_to_send}") \ No newline at end of file diff --git a/esp32/BLE_Keyboard/requirement.txt b/esp32/BLE_Keyboard/requirement.txt new file mode 100644 index 0000000..2eecc89 --- /dev/null +++ b/esp32/BLE_Keyboard/requirement.txt @@ -0,0 +1 @@ +keyboard pyserial \ No newline at end of file