├── BleCombo.cpp ├── BleCombo.h ├── library.properties ├── KeyboardOutputCallbacks.cpp ├── KeyboardOutputCallbacks.h ├── BleSecurityStatus.h ├── keywords.txt ├── BleConnectionStatus.h ├── BleSecurityStatus.cpp ├── BleComboMouse.h ├── BleComboMouse.cpp ├── BleConnectionStatus.cpp ├── examples └── KeyboardMouseExample │ └── KeyboardMouseExample.ino ├── README.md ├── BleComboKeyboard.h └── BleComboKeyboard.cpp /BleCombo.cpp: -------------------------------------------------------------------------------- 1 | #include "BleCombo.h" 2 | 3 | BleComboKeyboard Keyboard; 4 | BleComboMouse Mouse(&Keyboard); 5 | -------------------------------------------------------------------------------- /BleCombo.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_BLE_COMBO_H 2 | #define ESP32_BLE_COMBO_H 3 | #include "BleComboKeyboard.h" 4 | #include "BleComboMouse.h" 5 | 6 | extern BleComboMouse Mouse; 7 | extern BleComboKeyboard Keyboard; 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP32 BLE Combo Keyboard Mouse 2 | version=0.2.2 3 | author=T-vK 4 | maintainer=ServAlex 5 | sentence=Bluetooth LE Combo Keyboard Mouse library for the ESP32. 6 | paragraph=Bluetooth LE Combo Keyboard Mouse library for the ESP32. 7 | category=Communication 8 | url=https://github.com/ServAlex/ESP32-BLE-Combo 9 | architectures=esp32 10 | -------------------------------------------------------------------------------- /KeyboardOutputCallbacks.cpp: -------------------------------------------------------------------------------- 1 | #include "KeyboardOutputCallbacks.h" 2 | 3 | #if defined(CONFIG_ARDUHAL_ESP_LOG) 4 | #include "esp32-hal-log.h" 5 | #define LOG_TAG "" 6 | #else 7 | #include "esp_log.h" 8 | static const char* LOG_TAG = "BLEDevice"; 9 | #endif 10 | 11 | KeyboardOutputCallbacks::KeyboardOutputCallbacks(void) { 12 | } 13 | 14 | void KeyboardOutputCallbacks::onWrite(BLECharacteristic* me) { 15 | uint8_t* value = (uint8_t*)(me->getValue().c_str()); 16 | ESP_LOGI(LOG_TAG, "special keys: %d", *value); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /KeyboardOutputCallbacks.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_BLE_KEYBOARD_OUTPUT_CALLBACKS_H 2 | #define ESP32_BLE_KEYBOARD_OUTPUT_CALLBACKS_H 3 | #include "sdkconfig.h" 4 | #if defined(CONFIG_BT_ENABLED) 5 | 6 | #include 7 | #include "BLE2902.h" 8 | #include "BLECharacteristic.h" 9 | 10 | class KeyboardOutputCallbacks : public BLECharacteristicCallbacks 11 | { 12 | public: 13 | KeyboardOutputCallbacks(void); 14 | void onWrite(BLECharacteristic* me); 15 | }; 16 | 17 | #endif // CONFIG_BT_ENABLED 18 | #endif // ESP32_BLE_KEYBOARD_OUTPUT_CALLBACKS_H 19 | -------------------------------------------------------------------------------- /BleSecurityStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_BLE_SECURITY_STATUS_H 2 | #define ESP32_BLE_SECURITY_STATUS_H 3 | #include "sdkconfig.h" 4 | #if defined(CONFIG_BT_ENABLED) 5 | 6 | #include 7 | 8 | class BleSecurityStatus : public BLESecurityCallbacks { 9 | public: 10 | BleSecurityStatus(void); 11 | bool authenticated = false; 12 | uint32_t onPassKeyRequest(); 13 | void onPassKeyNotify(uint32_t pass_key); 14 | bool onSecurityRequest(); 15 | void onAuthenticationComplete(esp_ble_auth_cmpl_t); 16 | bool onConfirmPIN(uint32_t pin); 17 | }; 18 | 19 | #endif // CONFIG_BT_ENABLED 20 | #endif // ESP32_BLE_SECURITY_STATUS_H 21 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For ESP32 BLE Keyboard 3 | ####################################### 4 | # Class 5 | ####################################### 6 | 7 | BleComboKeyboard KEYWORD1 8 | BleComboMouse KEYWORD1 9 | 10 | ####################################### 11 | # Methods and Functions 12 | ####################################### 13 | 14 | begin KEYWORD2 15 | end KEYWORD2 16 | write KEYWORD2 17 | press KEYWORD2 18 | release KEYWORD2 19 | releaseAll KEYWORD2 20 | setBatteryLevel KEYWORD2 21 | isConnected KEYWORD2 22 | move KEYWORD2 23 | 24 | ####################################### 25 | # Constants 26 | ####################################### 27 | -------------------------------------------------------------------------------- /BleConnectionStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_BLE_CONNECTION_STATUS_H 2 | #define ESP32_BLE_CONNECTION_STATUS_H 3 | #include "sdkconfig.h" 4 | #if defined(CONFIG_BT_ENABLED) 5 | 6 | #include 7 | #include "BLE2902.h" 8 | #include "BLECharacteristic.h" 9 | 10 | class BleConnectionStatus : public BLEServerCallbacks 11 | { 12 | public: 13 | BleConnectionStatus(void); 14 | bool connected = false; 15 | void onConnect(BLEServer* pServer); 16 | void onDisconnect(BLEServer* pServer); 17 | BLECharacteristic* inputKeyboard; 18 | BLECharacteristic* outputKeyboard; 19 | BLECharacteristic* inputMediaKeys; 20 | BLECharacteristic* inputMouse; 21 | }; 22 | 23 | #endif // CONFIG_BT_ENABLED 24 | #endif // ESP32_BLE_CONNECTION_STATUS_H 25 | -------------------------------------------------------------------------------- /BleSecurityStatus.cpp: -------------------------------------------------------------------------------- 1 | #include "BleSecurityStatus.h" 2 | 3 | #include 4 | 5 | BleSecurityStatus::BleSecurityStatus(void) { 6 | } 7 | 8 | uint32_t BleSecurityStatus::onPassKeyRequest() { 9 | Serial.println("PassKey Request"); 10 | return 123456; 11 | } 12 | 13 | void BleSecurityStatus::onPassKeyNotify(uint32_t pass_key) { 14 | Serial.print("The passkey Notify number: "); 15 | Serial.println(pass_key); 16 | } 17 | 18 | bool BleSecurityStatus::onConfirmPIN(uint32_t pass_key) { 19 | Serial.print("The passkey YES/NO number: "); 20 | Serial.println(pass_key); 21 | vTaskDelay(5000); 22 | return true; 23 | } 24 | 25 | bool BleSecurityStatus::onSecurityRequest() { 26 | this->authenticated = false; 27 | Serial.println("Security Request"); 28 | return true; 29 | } 30 | 31 | void BleSecurityStatus::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) { 32 | Serial.println("Authentication Complete"); 33 | this->authenticated = true; 34 | } -------------------------------------------------------------------------------- /BleComboMouse.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_BLE_COMBO_MOUSE_H 2 | #define ESP32_BLE_COMBO_MOUSE_H 3 | #include "BleComboKeyboard.h" 4 | 5 | #define MOUSE_LEFT 1 6 | #define MOUSE_RIGHT 2 7 | #define MOUSE_MIDDLE 4 8 | #define MOUSE_BACK 8 9 | #define MOUSE_FORWARD 16 10 | #define MOUSE_ALL (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE) # For compatibility with the Mouse library 11 | 12 | class BleComboMouse { 13 | private: 14 | BleComboKeyboard* _keyboard; 15 | uint8_t _buttons; 16 | void buttons(uint8_t b); 17 | public: 18 | BleComboMouse(BleComboKeyboard* keyboard) { _keyboard = keyboard; }; 19 | void begin(void) {}; 20 | void end(void) {}; 21 | void click(uint8_t b = MOUSE_LEFT); 22 | void move(signed char x, signed char y, signed char wheel = 0, signed char hWheel = 0); 23 | void press(uint8_t b = MOUSE_LEFT); // press LEFT by default 24 | void release(uint8_t b = MOUSE_LEFT); // release LEFT by default 25 | bool isPressed(uint8_t b = MOUSE_LEFT); // check LEFT by default 26 | }; 27 | 28 | #endif // ESP32_BLE_COMBO_MOUSE_H 29 | -------------------------------------------------------------------------------- /BleComboMouse.cpp: -------------------------------------------------------------------------------- 1 | #include "BleComboMouse.h" 2 | 3 | void BleComboMouse::click(uint8_t b) 4 | { 5 | _buttons = b; 6 | move(0,0,0,0); 7 | _buttons = 0; 8 | move(0,0,0,0); 9 | } 10 | 11 | void BleComboMouse::move(signed char x, signed char y, signed char wheel, signed char hWheel) 12 | { 13 | if (_keyboard->isConnected()) 14 | { 15 | uint8_t m[5]; 16 | m[0] = _buttons; 17 | m[1] = x; 18 | m[2] = y; 19 | m[3] = wheel; 20 | m[4] = hWheel; 21 | _keyboard->inputMouse->setValue(m, 5); 22 | _keyboard->inputMouse->notify(); 23 | } 24 | } 25 | 26 | void BleComboMouse::buttons(uint8_t b) 27 | { 28 | if (b != _buttons) 29 | { 30 | _buttons = b; 31 | move(0,0,0,0); 32 | } 33 | } 34 | 35 | void BleComboMouse::press(uint8_t b) 36 | { 37 | buttons(_buttons | b); 38 | } 39 | 40 | void BleComboMouse::release(uint8_t b) 41 | { 42 | buttons(_buttons & ~b); 43 | } 44 | 45 | bool BleComboMouse::isPressed(uint8_t b) 46 | { 47 | if ((b & _buttons) > 0) 48 | return true; 49 | return false; 50 | } 51 | -------------------------------------------------------------------------------- /BleConnectionStatus.cpp: -------------------------------------------------------------------------------- 1 | #include "BleConnectionStatus.h" 2 | 3 | BleConnectionStatus::BleConnectionStatus(void) { 4 | } 5 | 6 | void BleConnectionStatus::onConnect(BLEServer* pServer) 7 | { 8 | this->connected = true; 9 | BLE2902* desc = (BLE2902*)this->inputKeyboard->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 10 | desc->setNotifications(true); 11 | desc = (BLE2902*)this->inputMouse->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 12 | desc->setNotifications(true); 13 | desc = (BLE2902*)this->inputMediaKeys->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 14 | desc->setNotifications(true); 15 | } 16 | 17 | void BleConnectionStatus::onDisconnect(BLEServer* pServer) 18 | { 19 | this->connected = false; 20 | BLE2902* desc = (BLE2902*)this->inputKeyboard->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 21 | desc->setNotifications(false); 22 | desc = (BLE2902*)this->inputMouse->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 23 | desc->setNotifications(false); 24 | desc = (BLE2902*)this->inputMediaKeys->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 25 | desc->setNotifications(false); 26 | } 27 | -------------------------------------------------------------------------------- /examples/KeyboardMouseExample/KeyboardMouseExample.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This example turns the ESP32 into a Bluetooth LE keyboard and mouse that writes 3 | * some words, presses Enter, presses a media key and then Ctrl+Alt+Delete, 4 | * then moves and the scrolls the mouse and clicks it. 5 | */ 6 | 7 | #include 8 | 9 | void setup() { 10 | Serial.begin(115200); 11 | Serial.println("Starting work!"); 12 | Keyboard.begin(); 13 | Mouse.begin(); 14 | } 15 | 16 | void loop() { 17 | if(Keyboard.isConnected()) { 18 | Serial.println("Sending 'Hello world'"); 19 | Keyboard.println("Hello World"); 20 | 21 | delay(1000); 22 | Serial.println("Sending Enter key..."); 23 | Keyboard.write(KEY_RETURN); 24 | 25 | delay(1000); 26 | 27 | Serial.println("Sending Play/Pause media key..."); 28 | Keyboard.write(KEY_MEDIA_PLAY_PAUSE); 29 | 30 | delay(1000); 31 | 32 | Serial.println("Sending Ctrl+Alt+Delete..."); 33 | Keyboard.press(KEY_LEFT_CTRL); 34 | Keyboard.press(KEY_LEFT_ALT); 35 | Keyboard.press(KEY_DELETE); 36 | delay(100); 37 | Keyboard.releaseAll(); 38 | 39 | unsigned long startTime; 40 | 41 | Serial.println("Move mouse pointer up"); 42 | startTime = millis(); 43 | while(millis() "Include Library" -> "Add .ZIP Library..." and select the file you just downloaded. 31 | - You can now go to "File" -> "Examples" -> "ESP32 BLE Combo" and select any of the examples to get started. 32 | VS Code: 33 | - In VS Code you can unzip downloaded .ZIP to your libraries directory (listed in your "includePath"). 34 | 35 | ## Example 36 | 37 | ```c++ 38 | #include 39 | #include 40 | 41 | void setup() { 42 | Serial.begin(115200); 43 | Keyboard.begin(); 44 | Mouse.begin(); 45 | 46 | Serial.println("The Bluetooth Device Is Ready To Pair"); 47 | 48 | // wait until client is connected 49 | while (!Keyboard.isAuthenticated()) { 50 | delay(100); 51 | } 52 | 53 | Serial.println("The Bluetooth Device Is Connected Successfully"); 54 | } 55 | 56 | void loop() { 57 | if (Keyboard.isConnected()) { 58 | Serial.println("Move mouse pointer up"); 59 | for (uint8_t i = 50; i > 0; i--) { 60 | Mouse.move(0, -10); 61 | delay(10); 62 | } 63 | 64 | delay(1000); 65 | 66 | Serial.println("Move mouse pointer down"); 67 | for (uint8_t i = 50; i > 0; i--) { 68 | Mouse.move(0, 10); 69 | delay(10); 70 | } 71 | } 72 | 73 | Serial.println("Waiting 2 seconds..."); 74 | delay(2000); 75 | } 76 | 77 | ``` 78 | ## Credits 79 | This is a fork of @blackketter's fork of @T-kV's excellent [ESP32-BLE-Mouse](https://github.com/T-vK/ESP32-BLE-Mouse) 80 | and [ESP32-BLE-Keyboard](https://github.com/T-vK/ESP32-BLE-Keyboard) libraries with fixes backported (up to December 2020). 81 | -------------------------------------------------------------------------------- /BleComboKeyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_BLE_COMBO_KEYBOARD_H 2 | #define ESP32_BLE_COMBO_KEYBOARD_H 3 | #include "sdkconfig.h" 4 | #if defined(CONFIG_BT_ENABLED) 5 | 6 | #include "BleConnectionStatus.h" 7 | #include "BleSecurityStatus.h" 8 | #include "BLEHIDDevice.h" 9 | #include "BLECharacteristic.h" 10 | #include "Print.h" 11 | 12 | 13 | const uint8_t KEY_LEFT_CTRL = 0x80; 14 | const uint8_t KEY_LEFT_SHIFT = 0x81; 15 | const uint8_t KEY_LEFT_ALT = 0x82; 16 | const uint8_t KEY_LEFT_GUI = 0x83; 17 | const uint8_t KEY_RIGHT_CTRL = 0x84; 18 | const uint8_t KEY_RIGHT_SHIFT = 0x85; 19 | const uint8_t KEY_RIGHT_ALT = 0x86; 20 | const uint8_t KEY_RIGHT_GUI = 0x87; 21 | 22 | const uint8_t KEY_UP_ARROW = 0xDA; 23 | const uint8_t KEY_DOWN_ARROW = 0xD9; 24 | const uint8_t KEY_LEFT_ARROW = 0xD8; 25 | const uint8_t KEY_RIGHT_ARROW = 0xD7; 26 | const uint8_t KEY_BACKSPACE = 0xB2; 27 | const uint8_t KEY_TAB = 0xB3; 28 | const uint8_t KEY_RETURN = 0xB0; 29 | const uint8_t KEY_ESC = 0xB1; 30 | const uint8_t KEY_INSERT = 0xD1; 31 | const uint8_t KEY_DELETE = 0xD4; 32 | const uint8_t KEY_PAGE_UP = 0xD3; 33 | const uint8_t KEY_PAGE_DOWN = 0xD6; 34 | const uint8_t KEY_HOME = 0xD2; 35 | const uint8_t KEY_END = 0xD5; 36 | const uint8_t KEY_CAPS_LOCK = 0xC1; 37 | const uint8_t KEY_F1 = 0xC2; 38 | const uint8_t KEY_F2 = 0xC3; 39 | const uint8_t KEY_F3 = 0xC4; 40 | const uint8_t KEY_F4 = 0xC5; 41 | const uint8_t KEY_F5 = 0xC6; 42 | const uint8_t KEY_F6 = 0xC7; 43 | const uint8_t KEY_F7 = 0xC8; 44 | const uint8_t KEY_F8 = 0xC9; 45 | const uint8_t KEY_F9 = 0xCA; 46 | const uint8_t KEY_F10 = 0xCB; 47 | const uint8_t KEY_F11 = 0xCC; 48 | const uint8_t KEY_F12 = 0xCD; 49 | const uint8_t KEY_F13 = 0xF0; 50 | const uint8_t KEY_F14 = 0xF1; 51 | const uint8_t KEY_F15 = 0xF2; 52 | const uint8_t KEY_F16 = 0xF3; 53 | const uint8_t KEY_F17 = 0xF4; 54 | const uint8_t KEY_F18 = 0xF5; 55 | const uint8_t KEY_F19 = 0xF6; 56 | const uint8_t KEY_F20 = 0xF7; 57 | const uint8_t KEY_F21 = 0xF8; 58 | const uint8_t KEY_F22 = 0xF9; 59 | const uint8_t KEY_F23 = 0xFA; 60 | const uint8_t KEY_F24 = 0xFB; 61 | 62 | typedef uint8_t MediaKeyReport[2]; 63 | 64 | const MediaKeyReport KEY_MEDIA_NEXT_TRACK = {1, 0}; 65 | const MediaKeyReport KEY_MEDIA_PREVIOUS_TRACK = {2, 0}; 66 | const MediaKeyReport KEY_MEDIA_STOP = {4, 0}; 67 | const MediaKeyReport KEY_MEDIA_PLAY_PAUSE = {8, 0}; 68 | const MediaKeyReport KEY_MEDIA_MUTE = {16, 0}; 69 | const MediaKeyReport KEY_MEDIA_VOLUME_UP = {32, 0}; 70 | const MediaKeyReport KEY_MEDIA_VOLUME_DOWN = {64, 0}; 71 | const MediaKeyReport KEY_MEDIA_WWW_HOME = {128, 0}; 72 | const MediaKeyReport KEY_MEDIA_LOCAL_MACHINE_BROWSER = {0, 1}; // Opens "My Computer" on Windows 73 | const MediaKeyReport KEY_MEDIA_CALCULATOR = {0, 2}; 74 | const MediaKeyReport KEY_MEDIA_WWW_BOOKMARKS = {0, 4}; 75 | const MediaKeyReport KEY_MEDIA_WWW_SEARCH = {0, 8}; 76 | const MediaKeyReport KEY_MEDIA_WWW_STOP = {0, 16}; 77 | const MediaKeyReport KEY_MEDIA_WWW_BACK = {0, 32}; 78 | const MediaKeyReport KEY_MEDIA_CONSUMER_CONTROL_CONFIGURATION = {0, 64}; // Media Selection 79 | const MediaKeyReport KEY_MEDIA_EMAIL_READER = {0, 128}; 80 | 81 | 82 | // Low level key report: up to 6 keys and shift, ctrl etc at once 83 | typedef struct 84 | { 85 | uint8_t modifiers; 86 | uint8_t reserved; 87 | uint8_t keys[6]; 88 | } KeyReport; 89 | 90 | class BleComboKeyboard : public Print 91 | { 92 | private: 93 | BleConnectionStatus* connectionStatus; 94 | BleSecurityStatus* securityStatus; 95 | BLEHIDDevice* hid; 96 | BLECharacteristic* inputKeyboard; 97 | BLECharacteristic* outputKeyboard; 98 | BLECharacteristic* inputMediaKeys; 99 | 100 | KeyReport _keyReport; 101 | MediaKeyReport _mediaKeyReport; 102 | static void taskServer(void* pvParameter); 103 | 104 | public: 105 | BleComboKeyboard(std::string deviceName = "Web Bluetooth", std::string deviceManufacturer = "Espressif", uint8_t batteryLevel = 100); 106 | void begin(void); 107 | void end(void); 108 | void sendReport(KeyReport* keys); 109 | void sendReport(MediaKeyReport* keys); 110 | size_t press(uint8_t k); 111 | size_t press(const MediaKeyReport k); 112 | size_t release(uint8_t k); 113 | size_t release(const MediaKeyReport k); 114 | size_t write(uint8_t c); 115 | size_t write(const MediaKeyReport c); 116 | size_t write(const uint8_t *buffer, size_t size); 117 | 118 | void releaseAll(void); 119 | bool isConnected(void); 120 | bool isAuthenticated(void); 121 | void setBatteryLevel(uint8_t level); 122 | uint8_t batteryLevel; 123 | std::string deviceManufacturer; 124 | std::string deviceName; 125 | 126 | BLECharacteristic* inputMouse; 127 | protected: 128 | virtual void onStarted(BLEServer *pServer) { }; 129 | }; 130 | 131 | #endif // CONFIG_BT_ENABLED 132 | #endif // ESP32_BLE_COMBO_KEYBOARD_H 133 | -------------------------------------------------------------------------------- /BleComboKeyboard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "BLE2902.h" 5 | #include "BLEHIDDevice.h" 6 | #include "HIDTypes.h" 7 | #include 8 | #include "sdkconfig.h" 9 | 10 | #include "BleConnectionStatus.h" 11 | #include "BleSecurityStatus.h" 12 | #include "KeyboardOutputCallbacks.h" 13 | #include "BleComboKeyboard.h" 14 | 15 | #if defined(CONFIG_ARDUHAL_ESP_LOG) 16 | #include "esp32-hal-log.h" 17 | #define LOG_TAG "" 18 | #else 19 | #include "esp_log.h" 20 | static const char* LOG_TAG = "BLEDevice"; 21 | #endif 22 | 23 | 24 | // Report IDs: 25 | #define KEYBOARD_ID 0x01 26 | #define MEDIA_KEYS_ID 0x02 27 | #define MOUSE_ID 0x03 28 | 29 | static const uint8_t _hidReportDescriptor[] = { 30 | USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop Ctrls) 31 | USAGE(1), 0x06, // USAGE (Keyboard) 32 | COLLECTION(1), 0x01, // COLLECTION (Application) 33 | // ------------------------------------------------- Keyboard 34 | REPORT_ID(1), KEYBOARD_ID, // REPORT_ID (1) 35 | USAGE_PAGE(1), 0x07, // USAGE_PAGE (Kbrd/Keypad) 36 | USAGE_MINIMUM(1), 0xE0, // USAGE_MINIMUM (0xE0) 37 | USAGE_MAXIMUM(1), 0xE7, // USAGE_MAXIMUM (0xE7) 38 | LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0) 39 | LOGICAL_MAXIMUM(1), 0x01, // Logical Maximum (1) 40 | REPORT_SIZE(1), 0x01, // REPORT_SIZE (1) 41 | REPORT_COUNT(1), 0x08, // REPORT_COUNT (8) 42 | HIDINPUT(1), 0x02, // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 43 | REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) ; 1 byte (Reserved) 44 | REPORT_SIZE(1), 0x08, // REPORT_SIZE (8) 45 | HIDINPUT(1), 0x01, // INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 46 | REPORT_COUNT(1), 0x05, // REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) 47 | REPORT_SIZE(1), 0x01, // REPORT_SIZE (1) 48 | USAGE_PAGE(1), 0x08, // USAGE_PAGE (LEDs) 49 | USAGE_MINIMUM(1), 0x01, // USAGE_MINIMUM (0x01) ; Num Lock 50 | USAGE_MAXIMUM(1), 0x05, // USAGE_MAXIMUM (0x05) ; Kana 51 | HIDOUTPUT(1), 0x02, // OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 52 | REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) ; 3 bits (Padding) 53 | REPORT_SIZE(1), 0x03, // REPORT_SIZE (3) 54 | HIDOUTPUT(1), 0x01, // OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 55 | REPORT_COUNT(1), 0x06, // REPORT_COUNT (6) ; 6 bytes (Keys) 56 | REPORT_SIZE(1), 0x08, // REPORT_SIZE(8) 57 | LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM(0) 58 | LOGICAL_MAXIMUM(1), 0x65, // LOGICAL_MAXIMUM(0x65) ; 101 keys 59 | USAGE_PAGE(1), 0x07, // USAGE_PAGE (Kbrd/Keypad) 60 | USAGE_MINIMUM(1), 0x00, // USAGE_MINIMUM (0) 61 | USAGE_MAXIMUM(1), 0x65, // USAGE_MAXIMUM (0x65) 62 | HIDINPUT(1), 0x00, // INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 63 | END_COLLECTION(0), // END_COLLECTION 64 | // ------------------------------------------------- Media Keys 65 | USAGE_PAGE(1), 0x0C, // USAGE_PAGE (Consumer) 66 | USAGE(1), 0x01, // USAGE (Consumer Control) 67 | COLLECTION(1), 0x01, // COLLECTION (Application) 68 | REPORT_ID(1), MEDIA_KEYS_ID, // REPORT_ID (3) 69 | USAGE_PAGE(1), 0x0C, // USAGE_PAGE (Consumer) 70 | LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0) 71 | LOGICAL_MAXIMUM(1), 0x01, // LOGICAL_MAXIMUM (1) 72 | REPORT_SIZE(1), 0x01, // REPORT_SIZE (1) 73 | REPORT_COUNT(1), 0x10, // REPORT_COUNT (16) 74 | USAGE(1), 0xB5, // USAGE (Scan Next Track) ; bit 0: 1 75 | USAGE(1), 0xB6, // USAGE (Scan Previous Track) ; bit 1: 2 76 | USAGE(1), 0xB7, // USAGE (Stop) ; bit 2: 4 77 | USAGE(1), 0xCD, // USAGE (Play/Pause) ; bit 3: 8 78 | USAGE(1), 0xE2, // USAGE (Mute) ; bit 4: 16 79 | USAGE(1), 0xE9, // USAGE (Volume Increment) ; bit 5: 32 80 | USAGE(1), 0xEA, // USAGE (Volume Decrement) ; bit 6: 64 81 | USAGE(2), 0x23, 0x02, // Usage (WWW Home) ; bit 7: 128 82 | USAGE(2), 0x94, 0x01, // Usage (My Computer) ; bit 0: 1 83 | USAGE(2), 0x92, 0x01, // Usage (Calculator) ; bit 1: 2 84 | USAGE(2), 0x2A, 0x02, // Usage (WWW fav) ; bit 2: 4 85 | USAGE(2), 0x21, 0x02, // Usage (WWW search) ; bit 3: 8 86 | USAGE(2), 0x26, 0x02, // Usage (WWW stop) ; bit 4: 16 87 | USAGE(2), 0x24, 0x02, // Usage (WWW back) ; bit 5: 32 88 | USAGE(2), 0x83, 0x01, // Usage (Media sel) ; bit 6: 64 89 | USAGE(2), 0x8A, 0x01, // Usage (Mail) ; bit 7: 128 90 | HIDINPUT(1), 0x02, // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 91 | END_COLLECTION(0), // END_COLLECTION 92 | 93 | // ------------------------------------------------- Mouse 94 | USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop) 95 | USAGE(1), 0x02, // USAGE (Mouse) 96 | COLLECTION(1), 0x01, // COLLECTION (Application) 97 | USAGE(1), 0x01, // USAGE (Pointer) 98 | COLLECTION(1), 0x00, // COLLECTION (Physical) 99 | REPORT_ID(1), MOUSE_ID, // REPORT_ID (1) 100 | // ------------------------------------------------- Buttons (Left, Right, Middle, Back, Forward) 101 | USAGE_PAGE(1), 0x09, // USAGE_PAGE (Button) 102 | USAGE_MINIMUM(1), 0x01, // USAGE_MINIMUM (Button 1) 103 | USAGE_MAXIMUM(1), 0x05, // USAGE_MAXIMUM (Button 5) 104 | LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0) 105 | LOGICAL_MAXIMUM(1), 0x01, // LOGICAL_MAXIMUM (1) 106 | REPORT_SIZE(1), 0x01, // REPORT_SIZE (1) 107 | REPORT_COUNT(1), 0x05, // REPORT_COUNT (5) 108 | HIDINPUT(1), 0x02, // INPUT (Data, Variable, Absolute) ;5 button bits 109 | // ------------------------------------------------- Padding 110 | REPORT_SIZE(1), 0x03, // REPORT_SIZE (3) 111 | REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) 112 | HIDINPUT(1), 0x03, // INPUT (Constant, Variable, Absolute) ;3 bit padding 113 | // ------------------------------------------------- X/Y position, Wheel 114 | USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop) 115 | USAGE(1), 0x30, // USAGE (X) 116 | USAGE(1), 0x31, // USAGE (Y) 117 | USAGE(1), 0x38, // USAGE (Wheel) 118 | LOGICAL_MINIMUM(1), 0x81, // LOGICAL_MINIMUM (-127) 119 | LOGICAL_MAXIMUM(1), 0x7f, // LOGICAL_MAXIMUM (127) 120 | REPORT_SIZE(1), 0x08, // REPORT_SIZE (8) 121 | REPORT_COUNT(1), 0x03, // REPORT_COUNT (3) 122 | HIDINPUT(1), 0x06, // INPUT (Data, Variable, Relative) ;3 bytes (X,Y,Wheel) 123 | // ------------------------------------------------- Horizontal wheel 124 | USAGE_PAGE(1), 0x0c, // USAGE PAGE (Consumer Devices) 125 | USAGE(2), 0x38, 0x02, // USAGE (AC Pan) 126 | LOGICAL_MINIMUM(1), 0x81, // LOGICAL_MINIMUM (-127) 127 | LOGICAL_MAXIMUM(1), 0x7f, // LOGICAL_MAXIMUM (127) 128 | REPORT_SIZE(1), 0x08, // REPORT_SIZE (8) 129 | REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) 130 | HIDINPUT(1), 0x06, // INPUT (Data, Var, Rel) 131 | END_COLLECTION(0), // END_COLLECTION 132 | END_COLLECTION(0) // END_COLLECTION 133 | }; 134 | 135 | BleComboKeyboard::BleComboKeyboard(std::string deviceName, std::string deviceManufacturer, uint8_t batteryLevel) : hid(0) 136 | { 137 | this->deviceName = deviceName; 138 | this->deviceManufacturer = deviceManufacturer; 139 | this->batteryLevel = batteryLevel; 140 | this->connectionStatus = new BleConnectionStatus(); 141 | this->securityStatus = new BleSecurityStatus(); 142 | } 143 | 144 | void BleComboKeyboard::begin(void) 145 | { 146 | xTaskCreate(this->taskServer, "server", 20000, (void *)this, 5, NULL); 147 | } 148 | 149 | void BleComboKeyboard::end(void) 150 | { 151 | } 152 | 153 | bool BleComboKeyboard::isConnected(void) { 154 | return this->connectionStatus->connected; 155 | } 156 | 157 | bool BleComboKeyboard::isAuthenticated(void) { 158 | return this->securityStatus->authenticated; 159 | } 160 | 161 | void BleComboKeyboard::setBatteryLevel(uint8_t level) { 162 | this->batteryLevel = level; 163 | if (hid != 0) 164 | this->hid->setBatteryLevel(this->batteryLevel); 165 | } 166 | 167 | void BleComboKeyboard::taskServer(void* pvParameter) { 168 | BleComboKeyboard* bleKeyboardInstance = (BleComboKeyboard *) pvParameter; //static_cast(pvParameter); 169 | BLEDevice::init(bleKeyboardInstance->deviceName); 170 | BLEDevice::setSecurityCallbacks(bleKeyboardInstance->securityStatus); 171 | BLEServer *pServer = BLEDevice::createServer(); 172 | pServer->setCallbacks(bleKeyboardInstance->connectionStatus); 173 | 174 | bleKeyboardInstance->hid = new BLEHIDDevice(pServer); 175 | bleKeyboardInstance->inputKeyboard = bleKeyboardInstance->hid->inputReport(KEYBOARD_ID); // <-- input REPORTID from report map 176 | bleKeyboardInstance->outputKeyboard = bleKeyboardInstance->hid->outputReport(KEYBOARD_ID); 177 | bleKeyboardInstance->inputMediaKeys = bleKeyboardInstance->hid->inputReport(MEDIA_KEYS_ID); 178 | bleKeyboardInstance->connectionStatus->inputKeyboard = bleKeyboardInstance->inputKeyboard; 179 | bleKeyboardInstance->connectionStatus->outputKeyboard = bleKeyboardInstance->outputKeyboard; 180 | 181 | bleKeyboardInstance->inputMouse = bleKeyboardInstance->hid->inputReport(MOUSE_ID); // <-- input REPORTID from report map 182 | bleKeyboardInstance->connectionStatus->inputMouse = bleKeyboardInstance->inputMouse; 183 | bleKeyboardInstance->connectionStatus->inputMediaKeys = bleKeyboardInstance->inputMediaKeys; 184 | 185 | bleKeyboardInstance->outputKeyboard->setCallbacks(new KeyboardOutputCallbacks()); 186 | 187 | bleKeyboardInstance->hid->manufacturer()->setValue(bleKeyboardInstance->deviceManufacturer); 188 | 189 | bleKeyboardInstance->hid->pnp(0x02, 0xe502, 0xa111, 0x0210); 190 | bleKeyboardInstance->hid->hidInfo(0x00,0x01); 191 | 192 | BLESecurity *pSecurity = new BLESecurity(); 193 | 194 | pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); 195 | pSecurity->setCapability(ESP_IO_CAP_IO); 196 | 197 | bleKeyboardInstance->hid->reportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor)); 198 | bleKeyboardInstance->hid->startServices(); 199 | 200 | bleKeyboardInstance->onStarted(pServer); 201 | 202 | BLEAdvertising *pAdvertising = pServer->getAdvertising(); 203 | pAdvertising->setAppearance(HID_KEYBOARD); 204 | pAdvertising->addServiceUUID(bleKeyboardInstance->hid->hidService()->getUUID()); 205 | pAdvertising->setScanResponse(false); 206 | pAdvertising->start(); 207 | bleKeyboardInstance->hid->setBatteryLevel(bleKeyboardInstance->batteryLevel); 208 | 209 | ESP_LOGD(LOG_TAG, "Advertising started!"); 210 | vTaskDelay(portMAX_DELAY); //delay(portMAX_DELAY); 211 | } 212 | 213 | void BleComboKeyboard::sendReport(KeyReport* keys) 214 | { 215 | if (this->isConnected()) 216 | { 217 | this->inputKeyboard->setValue((uint8_t*)keys, sizeof(KeyReport)); 218 | this->inputKeyboard->notify(); 219 | } 220 | } 221 | 222 | void BleComboKeyboard::sendReport(MediaKeyReport* keys) 223 | { 224 | if (this->isConnected()) 225 | { 226 | this->inputMediaKeys->setValue((uint8_t*)keys, sizeof(MediaKeyReport)); 227 | this->inputMediaKeys->notify(); 228 | } 229 | } 230 | 231 | extern 232 | const uint8_t _asciimap[128] PROGMEM; 233 | 234 | #define SHIFT 0x80 235 | const uint8_t _asciimap[128] = 236 | { 237 | 0x00, // NUL 238 | 0x00, // SOH 239 | 0x00, // STX 240 | 0x00, // ETX 241 | 0x00, // EOT 242 | 0x00, // ENQ 243 | 0x00, // ACK 244 | 0x00, // BEL 245 | 0x2a, // BS Backspace 246 | 0x2b, // TAB Tab 247 | 0x28, // LF Enter 248 | 0x00, // VT 249 | 0x00, // FF 250 | 0x00, // CR 251 | 0x00, // SO 252 | 0x00, // SI 253 | 0x00, // DEL 254 | 0x00, // DC1 255 | 0x00, // DC2 256 | 0x00, // DC3 257 | 0x00, // DC4 258 | 0x00, // NAK 259 | 0x00, // SYN 260 | 0x00, // ETB 261 | 0x00, // CAN 262 | 0x00, // EM 263 | 0x00, // SUB 264 | 0x00, // ESC 265 | 0x00, // FS 266 | 0x00, // GS 267 | 0x00, // RS 268 | 0x00, // US 269 | 270 | 0x2c, // ' ' 271 | 0x1e|SHIFT, // ! 272 | 0x34|SHIFT, // " 273 | 0x20|SHIFT, // # 274 | 0x21|SHIFT, // $ 275 | 0x22|SHIFT, // % 276 | 0x24|SHIFT, // & 277 | 0x34, // ' 278 | 0x26|SHIFT, // ( 279 | 0x27|SHIFT, // ) 280 | 0x25|SHIFT, // * 281 | 0x2e|SHIFT, // + 282 | 0x36, // , 283 | 0x2d, // - 284 | 0x37, // . 285 | 0x38, // / 286 | 0x27, // 0 287 | 0x1e, // 1 288 | 0x1f, // 2 289 | 0x20, // 3 290 | 0x21, // 4 291 | 0x22, // 5 292 | 0x23, // 6 293 | 0x24, // 7 294 | 0x25, // 8 295 | 0x26, // 9 296 | 0x33|SHIFT, // : 297 | 0x33, // ; 298 | 0x36|SHIFT, // < 299 | 0x2e, // = 300 | 0x37|SHIFT, // > 301 | 0x38|SHIFT, // ? 302 | 0x1f|SHIFT, // @ 303 | 0x04|SHIFT, // A 304 | 0x05|SHIFT, // B 305 | 0x06|SHIFT, // C 306 | 0x07|SHIFT, // D 307 | 0x08|SHIFT, // E 308 | 0x09|SHIFT, // F 309 | 0x0a|SHIFT, // G 310 | 0x0b|SHIFT, // H 311 | 0x0c|SHIFT, // I 312 | 0x0d|SHIFT, // J 313 | 0x0e|SHIFT, // K 314 | 0x0f|SHIFT, // L 315 | 0x10|SHIFT, // M 316 | 0x11|SHIFT, // N 317 | 0x12|SHIFT, // O 318 | 0x13|SHIFT, // P 319 | 0x14|SHIFT, // Q 320 | 0x15|SHIFT, // R 321 | 0x16|SHIFT, // S 322 | 0x17|SHIFT, // T 323 | 0x18|SHIFT, // U 324 | 0x19|SHIFT, // V 325 | 0x1a|SHIFT, // W 326 | 0x1b|SHIFT, // X 327 | 0x1c|SHIFT, // Y 328 | 0x1d|SHIFT, // Z 329 | 0x2f, // [ 330 | 0x31, // bslash 331 | 0x30, // ] 332 | 0x23|SHIFT, // ^ 333 | 0x2d|SHIFT, // _ 334 | 0x35, // ` 335 | 0x04, // a 336 | 0x05, // b 337 | 0x06, // c 338 | 0x07, // d 339 | 0x08, // e 340 | 0x09, // f 341 | 0x0a, // g 342 | 0x0b, // h 343 | 0x0c, // i 344 | 0x0d, // j 345 | 0x0e, // k 346 | 0x0f, // l 347 | 0x10, // m 348 | 0x11, // n 349 | 0x12, // o 350 | 0x13, // p 351 | 0x14, // q 352 | 0x15, // r 353 | 0x16, // s 354 | 0x17, // t 355 | 0x18, // u 356 | 0x19, // v 357 | 0x1a, // w 358 | 0x1b, // x 359 | 0x1c, // y 360 | 0x1d, // z 361 | 0x2f|SHIFT, // { 362 | 0x31|SHIFT, // | 363 | 0x30|SHIFT, // } 364 | 0x35|SHIFT, // ~ 365 | 0 // DEL 366 | }; 367 | 368 | 369 | uint8_t USBPutChar(uint8_t c); 370 | 371 | // press() adds the specified key (printing, non-printing, or modifier) 372 | // to the persistent key report and sends the report. Because of the way 373 | // USB HID works, the host acts like the key remains pressed until we 374 | // call release(), releaseAll(), or otherwise clear the report and resend. 375 | size_t BleComboKeyboard::press(uint8_t k) 376 | { 377 | uint8_t i; 378 | if (k >= 136) { // it's a non-printing key (not a modifier) 379 | k = k - 136; 380 | } else if (k >= 128) { // it's a modifier key 381 | _keyReport.modifiers |= (1<<(k-128)); 382 | k = 0; 383 | } else { // it's a printing key 384 | k = pgm_read_byte(_asciimap + k); 385 | if (!k) { 386 | setWriteError(); 387 | return 0; 388 | } 389 | if (k & 0x80) { // it's a capital letter or other character reached with shift 390 | _keyReport.modifiers |= 0x02; // the left shift modifier 391 | k &= 0x7F; 392 | } 393 | } 394 | 395 | // Add k to the key report only if it's not already present 396 | // and if there is an empty slot. 397 | if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && 398 | _keyReport.keys[2] != k && _keyReport.keys[3] != k && 399 | _keyReport.keys[4] != k && _keyReport.keys[5] != k) { 400 | 401 | for (i=0; i<6; i++) { 402 | if (_keyReport.keys[i] == 0x00) { 403 | _keyReport.keys[i] = k; 404 | break; 405 | } 406 | } 407 | if (i == 6) { 408 | setWriteError(); 409 | return 0; 410 | } 411 | } 412 | sendReport(&_keyReport); 413 | return 1; 414 | } 415 | 416 | size_t BleComboKeyboard::press(const MediaKeyReport k) 417 | { 418 | uint16_t k_16 = k[1] | (k[0] << 8); 419 | uint16_t mediaKeyReport_16 = _mediaKeyReport[1] | (_mediaKeyReport[0] << 8); 420 | 421 | mediaKeyReport_16 |= k_16; 422 | _mediaKeyReport[0] = (uint8_t)((mediaKeyReport_16 & 0xFF00) >> 8); 423 | _mediaKeyReport[1] = (uint8_t)(mediaKeyReport_16 & 0x00FF); 424 | 425 | sendReport(&_mediaKeyReport); 426 | return 1; 427 | } 428 | 429 | // release() takes the specified key out of the persistent key report and 430 | // sends the report. This tells the OS the key is no longer pressed and that 431 | // it shouldn't be repeated any more. 432 | size_t BleComboKeyboard::release(uint8_t k) 433 | { 434 | uint8_t i; 435 | if (k >= 136) { // it's a non-printing key (not a modifier) 436 | k = k - 136; 437 | } else if (k >= 128) { // it's a modifier key 438 | _keyReport.modifiers &= ~(1<<(k-128)); 439 | k = 0; 440 | } else { // it's a printing key 441 | k = pgm_read_byte(_asciimap + k); 442 | if (!k) { 443 | return 0; 444 | } 445 | if (k & 0x80) { // it's a capital letter or other character reached with shift 446 | _keyReport.modifiers &= ~(0x02); // the left shift modifier 447 | k &= 0x7F; 448 | } 449 | } 450 | 451 | // Test the key report to see if k is present. Clear it if it exists. 452 | // Check all positions in case the key is present more than once (which it shouldn't be) 453 | for (i=0; i<6; i++) { 454 | if (0 != k && _keyReport.keys[i] == k) { 455 | _keyReport.keys[i] = 0x00; 456 | } 457 | } 458 | 459 | sendReport(&_keyReport); 460 | return 1; 461 | } 462 | 463 | size_t BleComboKeyboard::release(const MediaKeyReport k) 464 | { 465 | uint16_t k_16 = k[1] | (k[0] << 8); 466 | uint16_t mediaKeyReport_16 = _mediaKeyReport[1] | (_mediaKeyReport[0] << 8); 467 | mediaKeyReport_16 &= ~k_16; 468 | _mediaKeyReport[0] = (uint8_t)((mediaKeyReport_16 & 0xFF00) >> 8); 469 | _mediaKeyReport[1] = (uint8_t)(mediaKeyReport_16 & 0x00FF); 470 | 471 | sendReport(&_mediaKeyReport); 472 | return 1; 473 | } 474 | 475 | void BleComboKeyboard::releaseAll(void) 476 | { 477 | _keyReport.keys[0] = 0; 478 | _keyReport.keys[1] = 0; 479 | _keyReport.keys[2] = 0; 480 | _keyReport.keys[3] = 0; 481 | _keyReport.keys[4] = 0; 482 | _keyReport.keys[5] = 0; 483 | _keyReport.modifiers = 0; 484 | _mediaKeyReport[0] = 0; 485 | _mediaKeyReport[1] = 0; 486 | sendReport(&_keyReport); 487 | } 488 | 489 | size_t BleComboKeyboard::write(uint8_t c) 490 | { 491 | uint8_t p = press(c); // Keydown 492 | release(c); // Keyup 493 | return p; // just return the result of press() since release() almost always returns 1 494 | } 495 | 496 | size_t BleComboKeyboard::write(const MediaKeyReport c) 497 | { 498 | uint16_t p = press(c); // Keydown 499 | release(c); // Keyup 500 | return p; // just return the result of press() since release() almost always returns 1 501 | } 502 | 503 | size_t BleComboKeyboard::write(const uint8_t *buffer, size_t size) { 504 | size_t n = 0; 505 | while (size--) { 506 | if (*buffer != '\r') { 507 | if (write(*buffer)) { 508 | n++; 509 | } else { 510 | break; 511 | } 512 | } 513 | buffer++; 514 | } 515 | return n; 516 | } 517 | 518 | --------------------------------------------------------------------------------