Refactor my code to a professional level by modularizing it exceptionally, eliminating redundancies and dead code, optimizing it for maximum efficiency, thoroughly documenting it with clear and concise comments, adding comprehensive unit tests with high coverage, applying consistent and clean styling following industry standards, adhering to best practices and design patterns, ensuring scalability and maintainability, following language-specific conventions, implementing robust error handling, enhancing security where applicable, optimizing performance, and providing a detailed summary of all changes made. Additionally, ensure the code is compatible with the latest standards and frameworks, includes meaningful variable and function names, removes any hard-coded values by using configuration files or environment variables, and verifies that all dependencies are up to date. To complete this task, reason step by step to deliver the best result. my code is in arduino ide, and i use a esp32 so i only can use one code. He always responds with the complete code without mistakes.
TASKS:
```
- implement code writing functions correctly, since keys that are not normal letters "a,b,c" do not work, such as space, enter, and all other keys.
- implement the letters with capital letters, you are using the shift when writing them, these must be recognized in the "ADD" commands, and be replaced by the corresponding key with shift.
- fully implement symbols, arrows and modifier keys.
- Leave aside the Function and Media keys for now.
```
THIS IS MY CODE:
```
// ========================
// ESP32 HID Controller - Corrected and Enhanced Version
// ========================

/**
 * @file HIDController.ino
 * @brief Enhanced ESP32 HID Controller using Arduino IDE.
 * 
 * This project implements HID actions based on gestures via BLE using an ESP32 microcontroller.
 * The code is organized into modular classes to ensure maintainability, scalability, and clarity.
 * Comprehensive comments and Doxygen-style documentation are provided throughout the code.
 */

// ========================
// Includes and Definitions
// ========================
#include <Arduino.h>
#include <BleCompositeHID.h>
#include <KeyboardDevice.h>
#include <Preferences.h>
#include <KeyboardHIDCodes.h>

// Forward declarations
class GestureManager;
class GestureExecutor;
class SerialCommandHandler;

// Logging Configuration
#define LOG_PREFIX "[HIDController] "

// Configuration Constants

// Device Information
constexpr char DEVICE_NAME[] = "ESP32 HIDC";
constexpr char DEVICE_MANUFACTURER[] = "YourManufacturer";

// Touch Pin Configuration
constexpr uint8_t TOUCH_PINS[] = { 4, 12, 13, 14, 15, 27, 32, 33 };
constexpr uint8_t TOUCH_THRESHOLDS[] = { 40, 40, 40, 40, 30, 40, 40, 50 };
constexpr uint8_t NUM_TOUCH_PINS = sizeof(TOUCH_PINS) / sizeof(TOUCH_PINS[0]);

// Gesture Parameters
constexpr uint16_t INACTIVITY_TIMEOUT_MS = 500;  // Inactivity timeout in milliseconds
constexpr uint8_t TOUCH_BUFFER_SIZE = 100;       // Maximum touch buffer size
constexpr uint8_t MAX_GESTURES = 255;            // Maximum number of gestures

// LED Configuration
constexpr uint8_t LED_PIN = LED_BUILTIN;
constexpr unsigned long LED_INACTIVITY_THRESHOLD_MS = 200;

// ========================
// Enumerations and Structures
// ========================

/**
 * @enum ActionType
 * @brief Represents the type of HID action to execute.
 */
enum class ActionType {
  NORMAL_KEY = 0,    ///< Standard keyboard key
  MEDIA_KEY,         ///< Media control key
  MODIFIER_PRESS,    ///< Modifier key press
  MODIFIER_RELEASE,  ///< Modifier key release
  TEXT               ///< Text input
};

/**
 * @struct Action
 * @brief Represents an individual HID action.
 */
struct Action {
  ActionType type;  ///< Type of action
  String parameter; ///< HID code or text

  Action()
    : type(ActionType::NORMAL_KEY), parameter("") {}
};

/**
 * @struct Gesture
 * @brief Represents a user-defined gesture.
 */
struct Gesture {
  uint8_t id;                      ///< Unique identifier for the gesture
  std::vector<uint8_t> pinSequence;///< Sequence of touch pins
  std::vector<Action> actions;     ///< Actions associated with the gesture

  Gesture()
    : id(0) {}
};

/**
 * @struct HIDPair
 * @brief Represents a mapping between a key string and its HID code.
 */
struct HIDPair {
  const char* key;  ///< Key string
  uint8_t value;    ///< Corresponding HID code
};

// ========================
// HID Mapping
// ========================

// HID Map stored in PROGMEM to save RAM
const HIDPair HIDMap[] PROGMEM = {
  // Special Keys
  { "SPACE", KEY_SPACE },
  { "ENTER", KEY_ENTER },
  { "ESCAPE", KEY_ESC },
  { "BACKSPACE", KEY_BACKSPACE },
  { "TAB", KEY_TAB },
  { "CAPSLOCK", KEY_CAPSLOCK },

  // Numbers
  { "0", KEY_0 },
  { "1", KEY_1 },
  { "2", KEY_2 },
  { "3", KEY_3 },
  { "4", KEY_4 },
  { "5", KEY_5 },
  { "6", KEY_6 },
  { "7", KEY_7 },
  { "8", KEY_8 },
  { "9", KEY_9 },

  // Letters
  { "A", KEY_A },
  { "B", KEY_B },
  { "C", KEY_C },
  { "D", KEY_D },
  { "E", KEY_E },
  { "F", KEY_F },
  { "G", KEY_G },
  { "H", KEY_H },
  { "I", KEY_I },
  { "J", KEY_J },
  { "K", KEY_K },
  { "L", KEY_L },
  { "M", KEY_M },
  { "N", KEY_N },
  { "O", KEY_O },
  { "P", KEY_P },
  { "Q", KEY_Q },
  { "R", KEY_R },
  { "S", KEY_S },
  { "T", KEY_T },
  { "U", KEY_U },
  { "V", KEY_V },
  { "W", KEY_W },
  { "X", KEY_X },
  { "Y", KEY_Y },
  { "Z", KEY_Z },

  // Symbols
  { "-", KEY_MINUS },
  { "=", KEY_EQUAL },
  { "[", KEY_LEFTBRACE },
  { "]", KEY_RIGHTBRACE },
  { "\\", KEY_BACKSLASH },
  { ";", KEY_SEMICOLON },
  { "'", KEY_APOSTROPHE },
  { ",", KEY_COMMA },
  { ".", KEY_DOT },
  { "/", KEY_SLASH },
  { "`", KEY_GRAVE },
  { "!", KEY_1 },
  { "@", KEY_2 },
  { "#", KEY_3 },
  { "$", KEY_4 },
  { "%", KEY_5 },
  { "^", KEY_6 },
  { "&", KEY_7 },
  { "*", KEY_8 },
  { "(", KEY_9 },
  { ")", KEY_0 },
  { "_", KEY_MINUS },
  { "+", KEY_EQUAL },
  { "{", KEY_LEFTBRACE },
  { "}", KEY_RIGHTBRACE },
  { "|", KEY_BACKSLASH },
  { ":", KEY_SEMICOLON },
  { "\"", KEY_APOSTROPHE },
  { "<", KEY_COMMA },
  { ">", KEY_DOT },
  { "?", KEY_SLASH },
  { "~", KEY_GRAVE },

  // Function Keys
  { "F1", KEY_F1 },
  { "F2", KEY_F2 },
  { "F3", KEY_F3 },
  { "F4", KEY_F4 },
  { "F5", KEY_F5 },
  { "F6", KEY_F6 },
  { "F7", KEY_F7 },
  { "F8", KEY_F8 },
  { "F9", KEY_F9 },
  { "F10", KEY_F10 },
  { "F11", KEY_F11 },
  { "F12", KEY_F12 },

  // Arrow Keys
  { "UP", KEY_UP },
  { "DOWN", KEY_DOWN },
  { "LEFT", KEY_LEFT },
  { "RIGHT", KEY_RIGHT },

  // Media Keys
  { "PLAY", KEY_MEDIA_PLAY },
  { "PAUSE", KEY_MEDIA_PAUSE },
  { "RECORD", KEY_MEDIA_RECORD },
  { "FASTFORWARD", KEY_MEDIA_FASTFORWARD },
  { "REWIND", KEY_MEDIA_REWIND },
  { "NEXTTRACK", KEY_MEDIA_NEXTTRACK },
  { "PREVIOUSTRACK", KEY_MEDIA_PREVIOUSTRACK },
  { "STOP", KEY_MEDIA_STOP },
  { "EJECT", KEY_MEDIA_EJECT },
  { "VOLUMEUP", KEY_MEDIA_VOLUMEUP },
  { "VOLUMEDOWN", KEY_MEDIA_VOLUMEDOWN },
  { "MUTE", KEY_MEDIA_MUTE },
  { "PLAYPAUSE", KEY_MEDIA_PLAYPAUSE },

  // Modifier Keys
  { "LCTRL", KEY_LEFTCTRL },
  { "LSHIFT", KEY_LEFTSHIFT },
  { "LALT", KEY_LEFTALT },
  { "LGUI", KEY_LEFTMETA },
  { "RCTRL", KEY_RIGHTCTRL },
  { "RSHIFT", KEY_RIGHTSHIFT },
  { "RALT", KEY_RIGHTALT },
  { "RGUI", KEY_RIGHTMETA }
};

constexpr size_t HIDMapSize = sizeof(HIDMap) / sizeof(HIDMap[0]);

// ========================
// Utility Functions
// ========================

/**
 * @brief Retrieves the HID code for a given action string.
 *        Performs a case-insensitive search.
 * @param action Action string.
 * @return HID code if found, 0 otherwise.
 */
uint8_t getHIDCode(const String& action) {
  if (action.length() == 0) return 0;

  String upperAction = action;
  upperAction.toUpperCase();

  for (size_t i = 0; i < HIDMapSize; i++) {
    HIDPair pair;
    memcpy_P(&pair, &HIDMap[i], sizeof(pair));
    if (upperAction == String(pair.key)) {
      return pair.value;
    }
  }
  return 0;  // Action not found
}

/**
 * @brief Determines if a character requires the Shift key.
 *        Includes a broader range of symbols.
 * @param c Character to check.
 * @return True if Shift is required, false otherwise.
 */
bool needsShift(char c) {
  if (isupper(c)) return true;

  const char symbolsRequiringShift[] = "!@#$%^&*()_+{}|:\"<>?~";
  for (uint8_t i = 0; i < sizeof(symbolsRequiringShift) - 1; i++) {
    if (c == symbolsRequiringShift[i]) return true;
  }
  return false;
}

// ========================
// Class Definitions
// ========================

/**
 * @class GestureStorage
 * @brief Handles storage and retrieval of gestures using Preferences.
 */
class GestureStorage {
public:
  /**
   * @brief Constructor for GestureStorage.
   * @param prefs Reference to the Preferences instance.
   */
  GestureStorage(Preferences& prefs)
    : preferences(prefs) {}

  /**
   * @brief Loads a gesture from preferences storage.
   * @param index Index of the gesture to load.
   * @param gesture Reference to a Gesture object to populate.
   * @return True if gesture loaded successfully, false otherwise.
   */
  bool loadGesture(uint8_t index, Gesture& gesture) {
    String idKey = "g" + String(index) + "_id";
    if (!preferences.isKey(idKey.c_str())) return false;

    gesture.id = static_cast<uint8_t>(preferences.getUInt(idKey.c_str(), 0));
    if (gesture.id == 0) return false;

    // Load pin sequence
    String seqKey = "g" + String(index) + "_seq";
    String sequenceStr = preferences.getString(seqKey.c_str(), "");
    gesture.pinSequence.clear();

    int start = 0;
    while (start < sequenceStr.length()) {
      int comma = sequenceStr.indexOf(',', start);
      String pinStr = (comma == -1) ? sequenceStr.substring(start) : sequenceStr.substring(start, comma);
      pinStr.trim();

      uint8_t pin = static_cast<uint8_t>(pinStr.toInt());
      if (pin != 0) {  // Valid pin
        gesture.pinSequence.push_back(pin);
      }

      if (comma == -1) break;
      start = comma + 1;
    }

    // Load actions
    String actKey = "g" + String(index) + "_acts";
    String actionsStr = preferences.getString(actKey.c_str(), "");
    gesture.actions.clear();
    start = 0;

    while (start < actionsStr.length()) {
      int semicolon = actionsStr.indexOf(';', start);
      String actionStr = (semicolon == -1) ? actionsStr.substring(start) : actionsStr.substring(start, semicolon);
      actionStr.trim();

      if (actionStr.length() > 0) {
        Action action;

        uint8_t hidCode = getHIDCode(actionStr);

        if (hidCode != 0) {
          // Determine action type
          if (hidCode >= 0xB0 && hidCode <= 0xF6) {
            action.type = ActionType::MEDIA_KEY;
            action.parameter = String(hidCode);
          } else if (
            hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT || hidCode == KEY_LEFTALT || hidCode == KEY_LEFTMETA ||
            hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT || hidCode == KEY_RIGHTALT || hidCode == KEY_RIGHTMETA) {
            action.type = ActionType::MODIFIER_PRESS;
            action.parameter = String(hidCode);
          } else {
            action.type = ActionType::NORMAL_KEY;
            action.parameter = String(hidCode);
          }
        } else {
          // It's text
          action.type = ActionType::TEXT;
          action.parameter = actionStr;
        }

        // Add action to gesture
        gesture.actions.push_back(action);
      }

      if (semicolon == -1) break;
      start = semicolon + 1;
    }

    // Additional validations
    return (!gesture.pinSequence.empty()) && (!gesture.actions.empty());
  }

  /**
   * @brief Saves a gesture to preferences storage.
   * @param index Index where the gesture should be saved.
   * @param gesture Gesture to save.
   * @return True if gesture saved successfully, false otherwise.
   */
  bool saveGesture(uint8_t index, const Gesture& gesture) {
    if (index >= MAX_GESTURES) return false;

    // Save ID
    String idKey = "g" + String(index) + "_id";
    if (!preferences.putUInt(idKey.c_str(), gesture.id)) {
      Serial.println(F(LOG_PREFIX "Failed to save gesture ID."));
      return false;
    }

    // Save pin sequence
    String seqKey = "g" + String(index) + "_seq";
    String seqBuffer = "";
    for (size_t j = 0; j < gesture.pinSequence.size(); j++) {
      seqBuffer += String(gesture.pinSequence[j]);
      if (j < gesture.pinSequence.size() - 1) {
        seqBuffer += ",";
      }
    }
    if (!preferences.putString(seqKey.c_str(), seqBuffer)) {
      Serial.println(F(LOG_PREFIX "Failed to save gesture pin sequence."));
      return false;
    }

    // Save actions
    String actKey = "g" + String(index) + "_acts";
    String actsBuffer = "";
    for (size_t a = 0; a < gesture.actions.size(); a++) {
      if (a > 0) actsBuffer += ";";
      if (gesture.actions[a].type == ActionType::TEXT) {
        actsBuffer += "\"" + gesture.actions[a].parameter + "\"";
      } else {
        actsBuffer += gesture.actions[a].parameter;
      }
    }
    if (!preferences.putString(actKey.c_str(), actsBuffer)) {
      Serial.println(F(LOG_PREFIX "Failed to save gesture actions."));
      return false;
    }

    return true;
  }

  /**
   * @brief Deletes a gesture by its index.
   * @param index Index of the gesture to delete.
   * @return True if deleted successfully, false otherwise.
   */
  bool deleteGesture(uint8_t index) {
    if (index >= getGestureCount()) return false;

    // Remove gesture entries
    String idKey = "g" + String(index) + "_id";
    String seqKey = "g" + String(index) + "_seq";
    String actKey = "g" + String(index) + "_acts";
    preferences.remove(idKey.c_str());
    preferences.remove(seqKey.c_str());
    preferences.remove(actKey.c_str());
    return true;
  }

  /**
   * @brief Retrieves the total number of stored gestures.
   * @return Number of gestures.
   */
  uint8_t getGestureCount() const {
    return static_cast<uint8_t>(preferences.getUInt("count", 0));
  }

  /**
   * @brief Sets the total number of gestures.
   * @param count Number of gestures to set.
   */
  void setGestureCount(uint8_t count) {
    preferences.putUInt("count", count);
  }

private:
  Preferences& preferences;  ///< Reference to Preferences instance
};

/**
 * @class GestureComparator
 * @brief Compares gesture sequences.
 */
class GestureComparator {
public:
  /**
   * @brief Compares two sequences.
   * @param haystack First sequence.
   * @param needle Second sequence.
   * @return True if the needle sequence is found at the end of haystack, false otherwise.
   */
  bool compareSequences(const std::vector<uint8_t>& haystack, const std::vector<uint8_t>& needle) {
    if (needle.empty() || haystack.size() < needle.size()) return false;

    auto it = haystack.end() - needle.size();
    return std::equal(needle.begin(), needle.end(), it);
  }
};

/**
 * @class KeyboardInput
 * @brief Handles sending keyboard and media inputs.
 */
class KeyboardInput {
public:
  /**
   * @brief Constructor for KeyboardInput.
   * @param kbDevice Pointer to the KeyboardDevice instance.
   */
  KeyboardInput(KeyboardDevice* kbDevice)
    : keyboard(kbDevice) {}

  /**
   * @brief Sends an action based on the Action object.
   * @param action The Action to execute.
   */
  void sendAction(const Action& action) {
    switch (action.type) {
      case ActionType::NORMAL_KEY:
        pressKey(action.parameter.toInt());
        break;
      case ActionType::MEDIA_KEY:
        pressMediaKey(action.parameter.toInt());
        break;
      case ActionType::MODIFIER_PRESS:
        pressModifier(action.parameter.toInt());
        break;
      case ActionType::MODIFIER_RELEASE:
        releaseModifier(action.parameter.toInt());
        break;
      case ActionType::TEXT:
        sendText(action.parameter);
        break;
      default:
        Serial.println(F(LOG_PREFIX "Unknown action type."));
        break;
    }
  }

private:
  KeyboardDevice* keyboard;                           ///< Pointer to the KeyboardDevice
  static constexpr uint16_t KEY_PRESS_DELAY_MS = 50;  ///< Delay between key press and release

  /**
   * @brief Presses and releases a normal key.
   * @param keyCode HID key code to press.
   */
  void pressKey(uint8_t keyCode) {
    Serial.print(F(LOG_PREFIX "Pressing key code: 0x"));
    Serial.println(keyCode, HEX);
    keyboard->keyPress(keyCode);
    delay(KEY_PRESS_DELAY_MS);
    keyboard->keyRelease(keyCode);
  }

  /**
   * @brief Presses and releases a media key.
   * @param mediaKey HID media key code to press.
   */
  void pressMediaKey(uint8_t mediaKey) {
    Serial.print(F(LOG_PREFIX "Pressing media key code: 0x"));
    Serial.println(mediaKey, HEX);
    keyboard->mediaKeyPress(static_cast<uint32_t>(mediaKey));
    delay(KEY_PRESS_DELAY_MS);
    keyboard->mediaKeyRelease(static_cast<uint32_t>(mediaKey));
  }

  /**
   * @brief Presses a modifier key.
   * @param modifier HID modifier key code to press.
   */
  void pressModifier(uint8_t modifier) {
    Serial.print(F(LOG_PREFIX "Pressing modifier key code: 0x"));
    Serial.println(modifier, HEX);
    keyboard->modifierKeyPress(modifier);
  }

  /**
   * @brief Releases a modifier key.
   * @param modifier HID modifier key code to release.
   */
  void releaseModifier(uint8_t modifier) {
    Serial.print(F(LOG_PREFIX "Releasing modifier key code: 0x"));
    Serial.println(modifier, HEX);
    keyboard->modifierKeyRelease(modifier);
  }

  /**
   * @brief Sends a text input.
   * @param text Text to send.
   */
  void sendText(const String& text) {
    Serial.print(F(LOG_PREFIX "Sending text: "));
    Serial.println(text);
    for (size_t c = 0; c < text.length(); c++) {
      char currentChar = text[c];
      bool shiftNeeded = needsShift(currentChar);
      uint8_t key = getHIDCode(String(currentChar));

      if (shiftNeeded) {
        pressModifier(KEY_LEFTSHIFT);
        Serial.println(F(LOG_PREFIX "Shift pressed."));
      }

      if (key != 0) {
        pressKey(key);
        Serial.print(F(LOG_PREFIX "Key sent: "));
        Serial.println(currentChar);
      } else {
        Serial.print(F(LOG_PREFIX "Failed to get HID code for character: "));
        Serial.println(currentChar);
      }

      if (shiftNeeded) {
        releaseModifier(KEY_LEFTSHIFT);
        Serial.println(F(LOG_PREFIX "Shift released."));
      }

      // Periodic memory check
      if (c % 10 == 0 && c != 0) {
        Serial.print(F(LOG_PREFIX "Free memory: "));
        Serial.println(ESP.getFreeHeap());
      }
    }
  }
};

/**
 * @class GestureExecutor
 * @brief Executes the actions defined in a gesture.
 */
class GestureExecutor {
public:
  /**
   * @brief Constructor for GestureExecutor.
   * @param kbInput Pointer to the KeyboardInput instance.
   */
  GestureExecutor(KeyboardInput* kbInput)
    : keyboardInput(kbInput) {}

  /**
   * @brief Executes the actions defined in a gesture.
   * @param gesture Gesture containing the actions to execute.
   */
  void executeActions(const Gesture& gesture) {
    Serial.print(F(LOG_PREFIX "Executing actions for gesture ID: "));
    Serial.println(gesture.id);
    for (const auto& action : gesture.actions) {
      keyboardInput->sendAction(action);
    }
  }

private:
  KeyboardInput* keyboardInput;  ///< Pointer to the KeyboardInput instance
};

/**
 * @class GestureManager
 * @brief Manages gestures including adding, deleting, and listing.
 */
class GestureManager {
public:
  /**
   * @brief Constructor for GestureManager.
   * @param storage Pointer to the GestureStorage instance.
   * @param comparator Pointer to the GestureComparator instance.
   */
  GestureManager(GestureStorage* storage, GestureComparator* comparator)
    : gestureStorage(storage), gestureComparator(comparator) {}

  /**
   * @brief Adds a new gesture.
   * @param gesture Gesture to add.
   * @return True if added successfully, false otherwise.
   */
  bool addGesture(const Gesture& gesture) {
    uint8_t currentCount = gestureStorage->getGestureCount();
    if (currentCount >= MAX_GESTURES) {
      Serial.println(F(LOG_PREFIX "Cannot add gesture: Maximum number of gestures reached."));
      return false;
    }

    if (!gestureStorage->saveGesture(currentCount, gesture)) {
      Serial.println(F(LOG_PREFIX "Failed to save gesture."));
      return false;
    }

    gestureStorage->setGestureCount(currentCount + 1);
    Serial.println(F(LOG_PREFIX "Gesture added successfully."));
    return true;
  }

  /**
   * @brief Deletes a gesture by its ID.
   * @param id ID of the gesture to delete.
   * @return True if deleted successfully, false otherwise.
   */
  bool deleteGesture(uint8_t id) {
    uint8_t gestureCount = gestureStorage->getGestureCount();
    for (uint8_t index = 0; index < gestureCount; index++) {
      Gesture gesture;
      if (!gestureStorage->loadGesture(index, gesture)) {
        Serial.println(F(LOG_PREFIX "Failed to load gesture."));
        continue;
      }

      if (gesture.id == id) {
        if (!gestureStorage->deleteGesture(index)) {
          Serial.println(F(LOG_PREFIX "Failed to delete gesture."));
          return false;
        }

        // Shift gestures to fill the gap
        for (uint8_t i = index; i < gestureCount - 1; i++) {
          Gesture nextGesture;
          if (!gestureStorage->loadGesture(i + 1, nextGesture)) {
            Serial.println(F(LOG_PREFIX "Failed to load next gesture during shift."));
            break;
          }
          gestureStorage->saveGesture(i, nextGesture);
        }

        // Remove the last gesture
        gestureStorage->deleteGesture(gestureCount - 1);
        gestureStorage->setGestureCount(gestureCount - 1);

        Serial.println(F(LOG_PREFIX "Gesture deleted successfully."));
        return true;
      }
    }

    Serial.println(F(LOG_PREFIX "Gesture with specified ID not found."));
    return false;
  }

  /**
   * @brief Lists all stored gestures via Serial.
   */
  void listGestures() const {
    uint8_t gestureCount = gestureStorage->getGestureCount();
    if (gestureCount == 0) {
      Serial.println(F(LOG_PREFIX "No gestures stored."));
      return;
    }

    Serial.println(F("=== List of Gestures ==="));
    Gesture gesture;
    for (uint8_t i = 0; i < gestureCount; i++) {
      if (!gestureStorage->loadGesture(i, gesture)) {
        Serial.println(F(LOG_PREFIX "Failed to load gesture."));
        continue;
      }

      Serial.print(F("ID: "));
      Serial.print(gesture.id);
      Serial.print(F(" | Sequence: "));

      for (size_t j = 0; j < gesture.pinSequence.size(); j++) {
        Serial.print(gesture.pinSequence[j]);
        if (j < gesture.pinSequence.size() - 1) {
          Serial.print(F(" -> "));
        }
      }

      Serial.print(F(" | Actions: "));
      for (size_t a = 0; a < gesture.actions.size(); a++) {
        const Action& action = gesture.actions[a];
        String actionType;
        switch (action.type) {
          case ActionType::NORMAL_KEY: actionType = "Key"; break;
          case ActionType::MEDIA_KEY: actionType = "Media Key"; break;
          case ActionType::MODIFIER_PRESS: actionType = "Modifier Press"; break;
          case ActionType::MODIFIER_RELEASE: actionType = "Modifier Release"; break;
          case ActionType::TEXT: actionType = "Text"; break;
          default: actionType = "Unknown"; break;
        }
        if (action.type != ActionType::TEXT) {
          Serial.print(actionType + "(0x" + String(action.parameter.toInt(), HEX) + ")");
        } else {
          Serial.print(actionType + "(\"" + action.parameter + "\")");
        }
        if (a < gesture.actions.size() - 1) {
          Serial.print(F(", "));
        }
      }
      Serial.println();  // New line after each gesture
    }
  }

  /**
   * @brief Finds and executes matching gestures based on the touch buffer.
   * @param touchBuffer Reference to the touch buffer.
   * @param executor Pointer to the GestureExecutor instance.
   */
  void findAndExecuteGestures(const std::vector<uint8_t>& touchBuffer, GestureExecutor* executor) {
    uint8_t gestureCount = gestureStorage->getGestureCount();
    if (gestureCount == 0) {
      Serial.println(F(LOG_PREFIX "No gestures stored."));
      return;
    }

    Gesture gesture;
    bool gestureFound = false;
    for (uint8_t i = 0; i < gestureCount; ++i) {
      if (!gestureStorage->loadGesture(i, gesture)) {
        Serial.println(F(LOG_PREFIX "Failed to load gesture."));
        continue;
      }

      if (gestureComparator->compareSequences(touchBuffer, gesture.pinSequence)) {
        Serial.print(F(LOG_PREFIX "Gesture matched with ID: "));
        Serial.println(gesture.id);

        executor->executeActions(gesture);
        Serial.println(F(LOG_PREFIX "Gesture actions executed."));
        gestureFound = true;
        // Continue searching for additional gestures
      }
    }

    if (!gestureFound) {
      Serial.println(F(LOG_PREFIX "No matching gesture found."));
    }
  }

  /**
   * @brief Retrieves the total number of stored gestures.
   * @return Number of gestures.
   */
  uint8_t getGestureCount() const {
    return gestureStorage->getGestureCount();
  }

  /**
   * @brief Load a gesture by its index.
   * @param index Index of the gesture.
   * @param gesture Reference to store the loaded gesture.
   * @return True if loaded successfully, false otherwise.
   */
  bool loadGesture(uint8_t index, Gesture& gesture) const {
    return gestureStorage->loadGesture(index, gesture);
  }

private:
  GestureStorage* gestureStorage;        ///< Pointer to GestureStorage instance
  GestureComparator* gestureComparator;  ///< Pointer to GestureComparator instance
};

/**
 * @class SerialCommandHandler
 * @brief Handles serial commands for managing gestures.
 */
class SerialCommandHandler {
public:
  /**
   * @brief Constructor for SerialCommandHandler.
   * @param gm Pointer to the GestureManager instance.
   */
  SerialCommandHandler(GestureManager* gm)
    : gestureManager(gm) {}

  /**
   * @brief Processes incoming serial commands.
   */
  void handleCommands() {
    while (Serial.available()) {
      String commandLine = Serial.readStringUntil('\n');
      commandLine.trim();

      if (commandLine.length() == 0) {
        logMessage(F("Empty command."));
        return;
      }

      int spaceIndex = commandLine.indexOf(' ');
      String command = (spaceIndex == -1) ? commandLine : commandLine.substring(0, spaceIndex);
      String params = (spaceIndex == -1) ? "" : commandLine.substring(spaceIndex + 1);

      command.toUpperCase();

      if (command == "ADD") {
        addCommandGesture(params);
      } else if (command == "DEL") {
        delCommandGesture(params);
      } else if (command == "LIST") {
        gestureManager->listGestures();
      } else if (command == "TEST") {
        logMessage("Free memory: " + String(ESP.getFreeHeap()) + " bytes");
      } else if (command == "HELP") {
        displayHelp();
      } else {
        logMessage(F("Unknown command. Type HELP for a list of commands."));
      }
    }
  }

private:
  GestureManager* gestureManager;  ///< Pointer to GestureManager

  /**
   * @brief Logs messages with a consistent format.
   * @param message Message to log.
   */
  void logMessage(const String& message) {
    Serial.print(LOG_PREFIX);
    Serial.println(message);
  }

  /**
   * @brief Adds a gesture based on serial command parameters.
   * @param params Parameters containing pin sequence and actions.
   */
  void addCommandGesture(const String& params) {
    int delimiterIndex = params.indexOf('|');
    if (delimiterIndex == -1) {
      logMessage(F("Invalid ADD format. Missing '|'. Usage: ADD <pin_sequence>|<actions>"));
      return;
    }

    String sequenceStr = params.substring(0, delimiterIndex);
    String actionsStr = params.substring(delimiterIndex + 1);

    // Create new gesture
    Gesture newGesture;
    newGesture.id = generateUniqueID();

    if (newGesture.id == 0) {
      logMessage(F("No available IDs for new gestures."));
      return;
    }

    // Process pin sequence
    sequenceStr.trim();
    std::vector<String> pinTokens = splitString(sequenceStr, ' ');
    for (const auto& pinToken : pinTokens) {
      uint8_t pin = static_cast<uint8_t>(pinToken.toInt());
      if (isValidPin(pin)) {
        newGesture.pinSequence.push_back(pin);
      } else {
        logMessage(String(F("Invalid pin ignored in sequence: ")) + pinToken);
      }
    }

    if (newGesture.pinSequence.empty()) {
      logMessage(F("No valid pins added to gesture."));
      return;
    }

    // Process actions
    actionsStr.trim();
    std::vector<String> actionTokens = splitActions(actionsStr);
    for (const auto& actionToken : actionTokens) {
      Action action;
      String trimmedToken = actionToken;
      trimmedToken.trim();

      if (trimmedToken.startsWith("\"") && trimmedToken.endsWith("\"")) {
        // Text action
        action.type = ActionType::TEXT;
        action.parameter = trimmedToken.substring(1, trimmedToken.length() - 1);
      } else {
        // Convert action token to HID code
        uint8_t hidCode = getHIDCode(trimmedToken);

        if (hidCode != 0) {
          // Determine action type
          if (hidCode >= 0xB0 && hidCode <= 0xF6) {
            action.type = ActionType::MEDIA_KEY;
            action.parameter = String(hidCode);
          } else if (
            hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT || hidCode == KEY_LEFTALT || hidCode == KEY_LEFTMETA ||
            hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT || hidCode == KEY_RIGHTALT || hidCode == KEY_RIGHTMETA) {
            action.type = ActionType::MODIFIER_PRESS;
            action.parameter = String(hidCode);
          } else {
            action.type = ActionType::NORMAL_KEY;
            action.parameter = String(hidCode);
          }
        } else {
          logMessage(String(F("Invalid action ignored: ")) + trimmedToken);
          continue;
        }
      }

      newGesture.actions.push_back(action);
    }

    if (newGesture.actions.empty()) {
      logMessage(F("No valid actions added to gesture."));
      return;
    }

    // Add gesture to storage
    if (!gestureManager->addGesture(newGesture)) {
      logMessage(F("Failed to add gesture."));
    }
  }

  /**
   * @brief Deletes a gesture based on its ID.
   * @param idStr String representing the ID.
   */
  void delCommandGesture(const String& idStr) {
    uint8_t id = static_cast<uint8_t>(idStr.toInt());
    if (!gestureManager->deleteGesture(id)) {
      logMessage(F("Gesture not found."));
    }
  }

  /**
   * @brief Generates a unique ID for a new gesture.
   * @return Unique ID if available, 0 otherwise.
   */
  uint8_t generateUniqueID() const {
    uint8_t gestureCount = gestureManager->getGestureCount();
    for (uint16_t id = 1; id <= MAX_GESTURES; id++) {
      bool exists = false;
      Gesture tempGesture;
      for (uint8_t i = 0; i < gestureCount; i++) {
        if (gestureManager->loadGesture(i, tempGesture)) {
          if (tempGesture.id == id) {
            exists = true;
            break;
          }
        }
      }
      if (!exists) return id;
    }
    return 0;  // No IDs available
  }

  /**
   * @brief Validates if the pin is within the predefined touch pins.
   * @param pin Pin number to validate.
   * @return True if valid, false otherwise.
   */
  bool isValidPin(uint8_t pin) const {
    for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
      uint8_t touchPin = TOUCH_PINS[i];
      if (pin == touchPin) {
        return true;
      }
    }
    return false;
  }

  /**
   * @brief Displays help information for available serial commands.
   */
  void displayHelp() const {
    Serial.println(F("Available Commands:"));
    Serial.println(F("ADD <pin_sequence>|<actions> - Add a new gesture"));
    Serial.println(F("DEL <ID>                 - Delete a gesture by ID"));
    Serial.println(F("LIST                     - List all stored gestures"));
    Serial.println(F("TEST                     - Check available memory"));
    Serial.println(F("HELP                     - Display this help message"));
  }

  /**
   * @brief Splits a string by a delimiter.
   * @param str The string to split.
   * @param delimiter The delimiter character.
   * @return A vector of substrings.
   */
  std::vector<String> splitString(const String& str, char delimiter) {
    std::vector<String> tokens;
    int start = 0;
    int end = str.indexOf(delimiter);
    while (end != -1) {
      tokens.push_back(str.substring(start, end));
      start = end + 1;
      end = str.indexOf(delimiter, start);
    }
    tokens.push_back(str.substring(start));
    return tokens;
  }

  /**
   * @brief Splits actions string into tokens, handling quoted text.
   * @param actionsStr The actions string.
   * @return A vector of action tokens.
   */
  std::vector<String> splitActions(const String& actionsStr) {
    std::vector<String> tokens;
    bool inQuotes = false;
    String currentToken = "";
    for (size_t i = 0; i < actionsStr.length(); i++) {
      char ch = actionsStr[i];
      if (ch == '\"') {
        inQuotes = !inQuotes;
        currentToken += ch;
      } else if (ch == ';' && !inQuotes) {
        tokens.push_back(currentToken);
        currentToken = "";
      } else {
        currentToken += ch;
      }
    }
    if (currentToken.length() > 0) {
      tokens.push_back(currentToken);
    }
    return tokens;
  }
};

/**
 * @class TouchHandler
 * @brief Handles touch inputs and LED feedback.
 */
class TouchHandler {
public:
  /**
   * @brief Constructor for TouchHandler.
   */
  TouchHandler()
    : previousTouchState(0), lastActivityTime(0) {}

  /**
   * @brief Initializes touch pins and LED.
   */
  void initialize() {
    for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
      uint8_t pin = TOUCH_PINS[i];
      pinMode(pin, INPUT);
    }
    pinMode(LED_PIN, OUTPUT);    // Configure built-in LED as output
    digitalWrite(LED_PIN, LOW);  // Turn off LED initially
  }

  /**
   * @brief Detects touch events.
   * @param touchedPin Pin number that was touched.
   * @return True if a touch was detected.
   */
  bool detectTouch(uint8_t& touchedPin) {
    for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
      uint8_t pin = TOUCH_PINS[i];
      uint8_t threshold = TOUCH_THRESHOLDS[i];
      bool isTouched = touchRead(pin) < threshold;

      // Detect change from not touched to touched
      if (isTouched && !(previousTouchState & (1 << i))) {
        touchedPin = pin;
        previousTouchState |= (1 << i);  // Mark as touched
        setLEDState(true);
        lastActivityTime = millis();
        return true;
      } else if (!isTouched && (previousTouchState & (1 << i))) {
        previousTouchState &= ~(1 << i);  // Mark as not touched
      }
    }

    // Turn off LED if inactivity exceeds threshold
    if (millis() - lastActivityTime > LED_INACTIVITY_THRESHOLD_MS) {
      setLEDState(false);
    }

    return false;
  }

private:
  uint8_t previousTouchState;      ///< Previous touch state bitmap
  unsigned long lastActivityTime;  ///< Last activity timestamp

  /**
   * @brief Sets the state of the built-in LED.
   * @param state True to turn on, false to turn off.
   */
  void setLEDState(bool state) {
    digitalWrite(LED_PIN, state ? HIGH : LOW);
  }
};

// ========================
// Auxiliary Functions
// ========================

/**
 * @brief Appends a pin to the touch buffer.
 * @param pin Pin number to add.
 * @param touchBuffer Reference to the touch buffer.
 */
void appendToBuffer(uint8_t pin, std::vector<uint8_t>& touchBuffer) {
  if (touchBuffer.size() >= TOUCH_BUFFER_SIZE) {
    touchBuffer.erase(touchBuffer.begin()); // Remove oldest
  }
  touchBuffer.push_back(pin);
}

/**
 * @brief Clears the touch buffer.
 * @param touchBuffer Reference to the touch buffer.
 */
void clearBuffer(std::vector<uint8_t>& touchBuffer) {
  touchBuffer.clear();
  Serial.println(F(LOG_PREFIX "Touch buffer cleared."));
}

// ========================
// Global Objects Initialization
// ========================

// Initialize Preferences
Preferences preferencesInstance;

// Initialize Gesture Storage and Comparator
GestureStorage gestureStorage(preferencesInstance);
GestureComparator gestureComparator;

// Initialize Gesture Manager
GestureManager gestureManager(&gestureStorage, &gestureComparator);

// Initialize Keyboard Device and Configuration
KeyboardDevice* keyboard = nullptr;   // Pointer to be initialized in setup()

// Initialize BLE Manager
BleCompositeHID compositeHID(DEVICE_NAME, DEVICE_MANUFACTURER, 50);

// Initialize Keyboard Input and Executor
KeyboardInput keyboardInput(nullptr); // Will set the keyboard instance in setup()
GestureExecutor gestureExecutor(&keyboardInput);

// Initialize Serial Command Handler
SerialCommandHandler serialHandler(&gestureManager);

// Initialize Touch Handler
TouchHandler touchHandler;

// ========================
// Setup Function
// ========================

/**
 * @brief Initializes the system.
 */
void setup() {
  Serial.begin(115200);
  while (!Serial) { /* Wait for serial port to be ready */ }

  Serial.println(F(LOG_PREFIX "Initializing system..."));

  // Initialize Preferences Storage
  if (!preferencesInstance.begin("gestures", false)) {
    Serial.println(F(LOG_PREFIX "Failed to initialize Preferences."));
    // Handle error appropriately, possibly enter a safe state or restart
    while (1);
  }

  // Initialize gesture count if not present
  if (!preferencesInstance.isKey("count")) {
    preferencesInstance.putUInt("count", 0);
  }

  // Initialize Keyboard Device and Configuration
  keyboard = new KeyboardDevice();

  if (keyboard == nullptr) {
    Serial.println(F(LOG_PREFIX "Failed to allocate KeyboardDevice. Entering safe state."));
    while (1); // Enter safe state
  }

  // Update keyboard input with the initialized keyboard
  keyboardInput = KeyboardInput(keyboard);

  // Initialize BLE
  compositeHID.addDevice(keyboard);
  compositeHID.begin();
  Serial.println(F(LOG_PREFIX "BLE initialized."));

  // Initialize Touch Handler
  touchHandler.initialize();

  // Indicate setup completion via LED
  digitalWrite(LED_PIN, HIGH);
  delay(1000);  // Keep LED on for 1 second
  digitalWrite(LED_PIN, LOW);

  Serial.println(F(LOG_PREFIX "System ready. Awaiting gestures..."));
}

// ========================
// Loop Function
// ========================

/**
 * @brief Main loop handling touch detection and serial commands.
 */
void loop() {
  static std::vector<uint8_t> touchBuffer;
  static unsigned long lastTouchTime = 0;

  uint8_t touchedPin = 0;
  bool touched = touchHandler.detectTouch(touchedPin);

  unsigned long currentTime = millis();

  if (touched) {
    appendToBuffer(touchedPin, touchBuffer);
    lastTouchTime = currentTime;
  }

  // If inactivity exceeds timeout, verify gestures
  if (!touchBuffer.empty() && (currentTime - lastTouchTime > INACTIVITY_TIMEOUT_MS)) {
    gestureManager.findAndExecuteGestures(touchBuffer, &gestureExecutor);
    clearBuffer(touchBuffer);
  }

  // Handle serial commands
  if (Serial.available()) {
    serialHandler.handleCommands();
  }
}
```
	codigos de librerias:
	```
	#######################################
	# Syntax Coloring Map For ESP32 BLE Gamepad
	#######################################
	# Class
	#######################################

	BleCompositeHID  KEYWORD1
	BleCompositeHIDConfiguration KEYWORD1

	#######################################
	# Methods and Functions
	#######################################

	begin	KEYWORD2
	end	KEYWORD2
	setAxes	KEYWORD2
	press	KEYWORD2
	release	KEYWORD2
	isPressed	KEYWORD2
	isConnected	KEYWORD2
	setLeftThumb KEYWORD2
	setRightThumb	KEYWORD2
	setLeftTrigger KEYWORD2
	setRightTrigger KEYWORD2
	setHat KEYWORD2
	setHat1	KEYWORD2
	setHat2	KEYWORD2
	setHat3	KEYWORD2
	setHat4	KEYWORD2
	setHats	KEYWORD2
	setX KEYWORD2
	setY KEYWORD2
	setZ KEYWORD2
	setRZ KEYWORD2
	setRX KEYWORD2
	setRY KEYWORD2
	setSlider1 KEYWORD2
	setSlider2 KEYWORD2
	setSlider KEYWORD2
	setSliders KEYWORD2
	setRudder	KEYWORD2
	setThrottle	KEYWORD2
	setAccelerator KEYWORD2
	setBrake KEYWORD2
	setSteering KEYWORD2 
	setSimulationControls	KEYWORD2
	setControllerType	KEYWORD2
	setAutoReport KEYWORD2
	setHidReportId KEYWORD2
	sendReport KEYWORD2
	setButtonCount KEYWORD2
	setHatSwitchCount KEYWORD2
	setIncludeStart KEYWORD2
	setIncludeSelect KEYWORD2
	setIncludeMenu KEYWORD2
	setIncludeHome KEYWORD2
	setIncludeBack KEYWORD2
	setIncludeVolumeInc KEYWORD2
	setIncludeVolumeDec KEYWORD2
	setIncludeVolumeMute KEYWORD2
	setWhichSpecialButtons KEYWORD2
	setIncludeXAxis KEYWORD2
	setIncludeYAxis KEYWORD2
	setIncludeZAxis KEYWORD2
	setIncludeRxAxis KEYWORD2
	setIncludeRyAxis KEYWORD2
	setIncludeRzAxis KEYWORD2
	setIncludeSlider1 KEYWORD2
	setIncludeSlider2 KEYWORD2
	setWhichAxes KEYWORD2
	setIncludeRudder KEYWORD2
	setIncludeThrottle KEYWORD2
	setIncludeAccelerator KEYWORD2
	setIncludeBrake KEYWORD2
	setIncludeSteering KEYWORD2
	setWhichSimulationControls KEYWORD2
	resetButtons KEYWORD2
	setBatteryLevel KEYWORD2
	pressSpecialButton KEYWORD2
	releaseSpecialButton KEYWORD2
	pressStart KEYWORD2
	releaseStart KEYWORD2
	pressSelect KEYWORD2
	releaseSelect KEYWORD2
	pressMenu KEYWORD2
	releaseMenu KEYWORD2
	pressHome KEYWORD2
	releaseHome KEYWORD2
	pressBack KEYWORD2
	releaseBack KEYWORD2
	pressVolumeInc KEYWORD2
	releaseVolumeInc KEYWORD2
	pressVolumeDec KEYWORD2
	releaseVolumeDec KEYWORD2
	pressVolumeMute KEYWORD2
	releaseVolumeMute KEYWORD2
	getControllerType KEYWORD2
	getHidReportId KEYWORD2
	getButtonCount KEYWORD2
	getTotalSpecialButtonCount KEYWORD2
	getDesktopSpecialButtonCount KEYWORD2
	getConsumerSpecialButtonCount KEYWORD2
	getHatSwitchCount KEYWORD2
	getAxisCount KEYWORD2
	getSimulationCount KEYWORD2
	getAutoReport KEYWORD2
	getIncludeStart KEYWORD2
	getIncludeSelect KEYWORD2
	getIncludeMenu KEYWORD2
	getIncludeHome KEYWORD2
	getIncludeBack KEYWORD2
	getIncludeVolumeInc KEYWORD2
	getIncludeVolumeDec KEYWORD2
	getIncludeVolumeMute KEYWORD2
	getWhichSpecialButtons KEYWORD2
	getIncludeXAxis KEYWORD2
	getIncludeYAxis KEYWORD2
	getIncludeZAxis KEYWORD2
	getIncludeRxAxis KEYWORD2
	getIncludeRyAxis KEYWORD2
	getIncludeRzAxis KEYWORD2
	getIncludeSlider1 KEYWORD2
	getIncludeSlider2 KEYWORD2
	getWhichAxes KEYWORD2
	getIncludeRudder KEYWORD2
	getIncludeThrottle KEYWORD2
	getIncludeAccelerator KEYWORD2
	getIncludeBrake KEYWORD2
	getIncludeSteering KEYWORD2
	getWhichSimulationControls KEYWORD2
	getVid KEYWORD2
	getPid KEYWORD2
	getGuidVersion KEYWORD2
	getAxesMin KEYWORD2
	getAxesMax KEYWORD2
	getSimulationMin KEYWORD2
	getSimulationMax KEYWORD2
	getModelNumber KEYWORD2
	getSoftwareRevision KEYWORD2
	getSerialNumber KEYWORD2
	getFirmwareRevision KEYWORD2
	getHardwareRevision KEYWORD2
	setVid KEYWORD2
	setPid KEYWORD2
	setGuidVersion KEYWORD2
	setAxesMin KEYWORD2
	setAxesMax KEYWORD2
	setSimulationMin KEYWORD2
	setSimulationMax KEYWORD2
	setModelNumber KEYWORD2
	setSoftwareRevision KEYWORD2
	setSerialNumber KEYWORD2
	setFirmwareRevision KEYWORD2
	setHardwareRevision KEYWORD2


	#######################################
	# Constants
	#######################################

	CONTROLLER_TYPE_JOYSTICK LITERAL1
	CONTROLLER_TYPE_GAMEPAD LITERAL1
	CONTROLLER_TYPE_MULTI_AXIS LITERAL1

	BUTTON_1 LITERAL1
	BUTTON_2 LITERAL1
	BUTTON_3 LITERAL1
	BUTTON_4 LITERAL1
	BUTTON_5 LITERAL1
	BUTTON_6 LITERAL1
	BUTTON_7 LITERAL1
	BUTTON_8 LITERAL1
	BUTTON_9 LITERAL1
	BUTTON_10 LITERAL1
	BUTTON_11 LITERAL1
	BUTTON_12 LITERAL1
	BUTTON_13 LITERAL1
	BUTTON_14 LITERAL1
	BUTTON_15 LITERAL1
	BUTTON_16 LITERAL1
	BUTTON_17 LITERAL1
	BUTTON_18 LITERAL1
	BUTTON_19 LITERAL1
	BUTTON_20 LITERAL1
	BUTTON_21 LITERAL1
	BUTTON_22 LITERAL1
	BUTTON_23 LITERAL1
	BUTTON_24 LITERAL1
	BUTTON_25 LITERAL1
	BUTTON_26 LITERAL1
	BUTTON_27 LITERAL1
	BUTTON_28 LITERAL1
	BUTTON_29 LITERAL1
	BUTTON_30 LITERAL1
	BUTTON_31 LITERAL1
	BUTTON_32 LITERAL1
	BUTTON_33 LITERAL1
	BUTTON_34 LITERAL1
	BUTTON_35 LITERAL1
	BUTTON_36 LITERAL1
	BUTTON_37 LITERAL1
	BUTTON_38 LITERAL1
	BUTTON_39 LITERAL1
	BUTTON_40 LITERAL1
	BUTTON_41 LITERAL1
	BUTTON_42 LITERAL1
	BUTTON_43 LITERAL1
	BUTTON_44 LITERAL1
	BUTTON_45 LITERAL1
	BUTTON_46 LITERAL1
	BUTTON_47 LITERAL1
	BUTTON_48 LITERAL1
	BUTTON_49 LITERAL1
	BUTTON_50 LITERAL1
	BUTTON_51 LITERAL1
	BUTTON_52 LITERAL1
	BUTTON_53 LITERAL1
	BUTTON_54 LITERAL1
	BUTTON_55 LITERAL1
	BUTTON_56 LITERAL1
	BUTTON_57 LITERAL1
	BUTTON_58 LITERAL1
	BUTTON_59 LITERAL1
	BUTTON_60 LITERAL1
	BUTTON_61 LITERAL1
	BUTTON_62 LITERAL1
	BUTTON_63 LITERAL1
	BUTTON_64 LITERAL1
	BUTTON_65 LITERAL1
	BUTTON_66 LITERAL1
	BUTTON_67 LITERAL1
	BUTTON_68 LITERAL1
	BUTTON_69 LITERAL1
	BUTTON_70 LITERAL1
	BUTTON_71 LITERAL1
	BUTTON_72 LITERAL1
	BUTTON_73 LITERAL1
	BUTTON_74 LITERAL1
	BUTTON_75 LITERAL1
	BUTTON_76 LITERAL1
	BUTTON_77 LITERAL1
	BUTTON_78 LITERAL1
	BUTTON_79 LITERAL1
	BUTTON_80 LITERAL1
	BUTTON_81 LITERAL1
	BUTTON_82 LITERAL1
	BUTTON_83 LITERAL1
	BUTTON_84 LITERAL1
	BUTTON_85 LITERAL1
	BUTTON_86 LITERAL1
	BUTTON_87 LITERAL1
	BUTTON_88 LITERAL1
	BUTTON_89 LITERAL1
	BUTTON_90 LITERAL1
	BUTTON_91 LITERAL1
	BUTTON_92 LITERAL1
	BUTTON_93 LITERAL1
	BUTTON_94 LITERAL1
	BUTTON_95 LITERAL1
	BUTTON_96 LITERAL1
	BUTTON_97 LITERAL1
	BUTTON_98 LITERAL1
	BUTTON_99 LITERAL1
	BUTTON_100 LITERAL1
	BUTTON_101 LITERAL1
	BUTTON_102 LITERAL1
	BUTTON_103 LITERAL1
	BUTTON_104 LITERAL1
	BUTTON_105 LITERAL1
	BUTTON_106 LITERAL1
	BUTTON_107 LITERAL1
	BUTTON_108 LITERAL1
	BUTTON_109 LITERAL1
	BUTTON_110 LITERAL1
	BUTTON_111 LITERAL1
	BUTTON_112 LITERAL1
	BUTTON_113 LITERAL1
	BUTTON_114 LITERAL1
	BUTTON_115 LITERAL1
	BUTTON_116 LITERAL1
	BUTTON_117 LITERAL1
	BUTTON_118 LITERAL1
	BUTTON_119 LITERAL1
	BUTTON_120 LITERAL1
	BUTTON_121 LITERAL1
	BUTTON_122 LITERAL1
	BUTTON_123 LITERAL1
	BUTTON_124 LITERAL1
	BUTTON_125 LITERAL1
	BUTTON_126 LITERAL1
	BUTTON_127 LITERAL1
	BUTTON_128 LITERAL1

	DPAD_CENTERED LITERAL1
	DPAD_UP LITERAL1
	DPAD_UP_RIGHT LITERAL1
	DPAD_RIGHT LITERAL1
	DPAD_DOWN_RIGHT LITERAL1
	DPAD_DOWN LITERAL1
	DPAD_DOWN_LEFT LITERAL1
	DPAD_LEFT LITERAL1
	DPAD_UP_LEFT LITERAL1

	HAT_CENTERED LITERAL1
	HAT_UP LITERAL1
	HAT_UP_RIGHT LITERAL1
	HAT_RIGHT LITERAL1
	HAT_DOWN_RIGHT LITERAL1
	HAT_DOWN LITERAL1
	HAT_DOWN_LEFT LITERAL1
	HAT_LEFT LITERAL1
	HAT_UP_LEFT LITERAL1
	```
	```
	/**
	 * USB HID Keyboard scan codes as per USB spec 1.11
	 * plus some additional codes
	 * 
	 * Created by MightyPork, 2016
	 * Public domain
	 * 
	 * Adapted from:
	 * https://source.android.com/devices/input/keyboard-devices.html
	 */

	#ifndef USB_HID_KEYS
	#define USB_HID_KEYS

	/**
	 * Modifier masks - used for the first byte in the HID report.
	 * NOTE: The second byte in the report is reserved, 0x00
	 */
	#define KEY_MOD_LCTRL  0x01
	#define KEY_MOD_LSHIFT 0x02
	#define KEY_MOD_LALT   0x04
	#define KEY_MOD_LMETA  0x08
	#define KEY_MOD_RCTRL  0x10
	#define KEY_MOD_RSHIFT 0x20
	#define KEY_MOD_RALT   0x40
	#define KEY_MOD_RMETA  0x80

	/**
	 * Scan codes - last N slots in the HID report (usually 6).
	 * 0x00 if no key pressed.
	 * 
	 * If more than N keys are pressed, the HID reports 
	 * KEY_ERR_OVF in all slots to indicate this condition.
	 */

	#define KEY_NONE 0x00 // No key pressed
	#define KEY_ERR_OVF 0x01 //  Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
	// 0x02 //  Keyboard POST Fail
	// 0x03 //  Keyboard Error Undefined
	#define KEY_A 0x04 // Keyboard a and A
	#define KEY_B 0x05 // Keyboard b and B
	#define KEY_C 0x06 // Keyboard c and C
	#define KEY_D 0x07 // Keyboard d and D
	#define KEY_E 0x08 // Keyboard e and E
	#define KEY_F 0x09 // Keyboard f and F
	#define KEY_G 0x0a // Keyboard g and G
	#define KEY_H 0x0b // Keyboard h and H
	#define KEY_I 0x0c // Keyboard i and I
	#define KEY_J 0x0d // Keyboard j and J
	#define KEY_K 0x0e // Keyboard k and K
	#define KEY_L 0x0f // Keyboard l and L
	#define KEY_M 0x10 // Keyboard m and M
	#define KEY_N 0x11 // Keyboard n and N
	#define KEY_O 0x12 // Keyboard o and O
	#define KEY_P 0x13 // Keyboard p and P
	#define KEY_Q 0x14 // Keyboard q and Q
	#define KEY_R 0x15 // Keyboard r and R
	#define KEY_S 0x16 // Keyboard s and S
	#define KEY_T 0x17 // Keyboard t and T
	#define KEY_U 0x18 // Keyboard u and U
	#define KEY_V 0x19 // Keyboard v and V
	#define KEY_W 0x1a // Keyboard w and W
	#define KEY_X 0x1b // Keyboard x and X
	#define KEY_Y 0x1c // Keyboard y and Y
	#define KEY_Z 0x1d // Keyboard z and Z

	#define KEY_1 0x1e // Keyboard 1 and !
	#define KEY_2 0x1f // Keyboard 2 and @
	#define KEY_3 0x20 // Keyboard 3 and #
	#define KEY_4 0x21 // Keyboard 4 and $
	#define KEY_5 0x22 // Keyboard 5 and %
	#define KEY_6 0x23 // Keyboard 6 and ^
	#define KEY_7 0x24 // Keyboard 7 and &
	#define KEY_8 0x25 // Keyboard 8 and *
	#define KEY_9 0x26 // Keyboard 9 and (
	#define KEY_0 0x27 // Keyboard 0 and )

	#define KEY_ENTER 0x28 // Keyboard Return (ENTER)
	#define KEY_ESC 0x29 // Keyboard ESCAPE
	#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
	#define KEY_TAB 0x2b // Keyboard Tab
	#define KEY_SPACE 0x2c // Keyboard Spacebar
	#define KEY_MINUS 0x2d // Keyboard - and _
	#define KEY_EQUAL 0x2e // Keyboard = and +
	#define KEY_LEFTBRACE 0x2f // Keyboard [ and {
	#define KEY_RIGHTBRACE 0x30 // Keyboard ] and }
	#define KEY_BACKSLASH 0x31 // Keyboard \ and |
	#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~
	#define KEY_SEMICOLON 0x33 // Keyboard ; and :
	#define KEY_APOSTROPHE 0x34 // Keyboard ' and "
	#define KEY_GRAVE 0x35 // Keyboard ` and ~
	#define KEY_COMMA 0x36 // Keyboard , and <
	#define KEY_DOT 0x37 // Keyboard . and >
	#define KEY_SLASH 0x38 // Keyboard / and ?
	#define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock

	#define KEY_F1 0x3a // Keyboard F1
	#define KEY_F2 0x3b // Keyboard F2
	#define KEY_F3 0x3c // Keyboard F3
	#define KEY_F4 0x3d // Keyboard F4
	#define KEY_F5 0x3e // Keyboard F5
	#define KEY_F6 0x3f // Keyboard F6
	#define KEY_F7 0x40 // Keyboard F7
	#define KEY_F8 0x41 // Keyboard F8
	#define KEY_F9 0x42 // Keyboard F9
	#define KEY_F10 0x43 // Keyboard F10
	#define KEY_F11 0x44 // Keyboard F11
	#define KEY_F12 0x45 // Keyboard F12

	#define KEY_SYSRQ 0x46 // Keyboard Print Screen
	#define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock
	#define KEY_PAUSE 0x48 // Keyboard Pause
	#define KEY_INSERT 0x49 // Keyboard Insert
	#define KEY_HOME 0x4a // Keyboard Home
	#define KEY_PAGEUP 0x4b // Keyboard Page Up
	#define KEY_DELETE 0x4c // Keyboard Delete Forward
	#define KEY_END 0x4d // Keyboard End
	#define KEY_PAGEDOWN 0x4e // Keyboard Page Down
	#define KEY_RIGHT 0x4f // Keyboard Right Arrow
	#define KEY_LEFT 0x50 // Keyboard Left Arrow
	#define KEY_DOWN 0x51 // Keyboard Down Arrow
	#define KEY_UP 0x52 // Keyboard Up Arrow

	#define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear
	#define KEY_KPSLASH 0x54 // Keypad /
	#define KEY_KPASTERISK 0x55 // Keypad *
	#define KEY_KPMINUS 0x56 // Keypad -
	#define KEY_KPPLUS 0x57 // Keypad +
	#define KEY_KPENTER 0x58 // Keypad ENTER
	#define KEY_KP1 0x59 // Keypad 1 and End
	#define KEY_KP2 0x5a // Keypad 2 and Down Arrow
	#define KEY_KP3 0x5b // Keypad 3 and PageDn
	#define KEY_KP4 0x5c // Keypad 4 and Left Arrow
	#define KEY_KP5 0x5d // Keypad 5
	#define KEY_KP6 0x5e // Keypad 6 and Right Arrow
	#define KEY_KP7 0x5f // Keypad 7 and Home
	#define KEY_KP8 0x60 // Keypad 8 and Up Arrow
	#define KEY_KP9 0x61 // Keypad 9 and Page Up
	#define KEY_KP0 0x62 // Keypad 0 and Insert
	#define KEY_KPDOT 0x63 // Keypad . and Delete

	#define KEY_102ND 0x64 // Keyboard Non-US \ and |
	#define KEY_COMPOSE 0x65 // Keyboard Application
	#define KEY_POWER 0x66 // Keyboard Power
	#define KEY_KPEQUAL 0x67 // Keypad =

	#define KEY_F13 0x68 // Keyboard F13
	#define KEY_F14 0x69 // Keyboard F14
	#define KEY_F15 0x6a // Keyboard F15
	#define KEY_F16 0x6b // Keyboard F16
	#define KEY_F17 0x6c // Keyboard F17
	#define KEY_F18 0x6d // Keyboard F18
	#define KEY_F19 0x6e // Keyboard F19
	#define KEY_F20 0x6f // Keyboard F20
	#define KEY_F21 0x70 // Keyboard F21
	#define KEY_F22 0x71 // Keyboard F22
	#define KEY_F23 0x72 // Keyboard F23
	#define KEY_F24 0x73 // Keyboard F24

	#define KEY_OPEN 0x74 // Keyboard Execute
	#define KEY_HELP 0x75 // Keyboard Help
	#define KEY_PROPS 0x76 // Keyboard Menu
	#define KEY_FRONT 0x77 // Keyboard Select
	#define KEY_STOP 0x78 // Keyboard Stop
	#define KEY_AGAIN 0x79 // Keyboard Again
	#define KEY_UNDO 0x7a // Keyboard Undo
	#define KEY_CUT 0x7b // Keyboard Cut
	#define KEY_COPY 0x7c // Keyboard Copy
	#define KEY_PASTE 0x7d // Keyboard Paste
	#define KEY_FIND 0x7e // Keyboard Find
	#define KEY_MUTE 0x7f // Keyboard Mute
	#define KEY_VOLUMEUP 0x80 // Keyboard Volume Up
	#define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down
	// 0x82  Keyboard Locking Caps Lock
	// 0x83  Keyboard Locking Num Lock
	// 0x84  Keyboard Locking Scroll Lock
	#define KEY_KPCOMMA 0x85 // Keypad Comma
	// 0x86  Keypad Equal Sign
	#define KEY_RO 0x87 // Keyboard International1
	#define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2
	#define KEY_YEN 0x89 // Keyboard International3
	#define KEY_HENKAN 0x8a // Keyboard International4
	#define KEY_MUHENKAN 0x8b // Keyboard International5
	#define KEY_KPJPCOMMA 0x8c // Keyboard International6
	// 0x8d  Keyboard International7
	// 0x8e  Keyboard International8
	// 0x8f  Keyboard International9
	#define KEY_HANGEUL 0x90 // Keyboard LANG1
	#define KEY_HANJA 0x91 // Keyboard LANG2
	#define KEY_KATAKANA 0x92 // Keyboard LANG3
	#define KEY_HIRAGANA 0x93 // Keyboard LANG4
	#define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5
	// 0x95  Keyboard LANG6
	// 0x96  Keyboard LANG7
	// 0x97  Keyboard LANG8
	// 0x98  Keyboard LANG9
	// 0x99  Keyboard Alternate Erase
	// 0x9a  Keyboard SysReq/Attention
	// 0x9b  Keyboard Cancel
	// 0x9c  Keyboard Clear
	// 0x9d  Keyboard Prior
	// 0x9e  Keyboard Return
	// 0x9f  Keyboard Separator
	// 0xa0  Keyboard Out
	// 0xa1  Keyboard Oper
	// 0xa2  Keyboard Clear/Again
	// 0xa3  Keyboard CrSel/Props
	// 0xa4  Keyboard ExSel

	// 0xb0  Keypad 00
	// 0xb1  Keypad 000
	// 0xb2  Thousands Separator
	// 0xb3  Decimal Separator
	// 0xb4  Currency Unit
	// 0xb5  Currency Sub-unit
	#define KEY_KPLEFTPAREN 0xb6 // Keypad (
	#define KEY_KPRIGHTPAREN 0xb7 // Keypad )
	// 0xb8  Keypad {
	// 0xb9  Keypad }
	// 0xba  Keypad Tab
	// 0xbb  Keypad Backspace
	// 0xbc  Keypad A
	// 0xbd  Keypad B
	// 0xbe  Keypad C
	// 0xbf  Keypad D
	// 0xc0  Keypad E
	// 0xc1  Keypad F
	// 0xc2  Keypad XOR
	// 0xc3  Keypad ^
	// 0xc4  Keypad %
	// 0xc5  Keypad <
	// 0xc6  Keypad >
	// 0xc7  Keypad &
	// 0xc8  Keypad &&
	// 0xc9  Keypad |
	// 0xca  Keypad ||
	// 0xcb  Keypad :
	// 0xcc  Keypad #
	// 0xcd  Keypad Space
	// 0xce  Keypad @
	// 0xcf  Keypad !
	// 0xd0  Keypad Memory Store
	// 0xd1  Keypad Memory Recall
	// 0xd2  Keypad Memory Clear
	// 0xd3  Keypad Memory Add
	// 0xd4  Keypad Memory Subtract
	// 0xd5  Keypad Memory Multiply
	// 0xd6  Keypad Memory Divide
	// 0xd7  Keypad +/-
	// 0xd8  Keypad Clear
	// 0xd9  Keypad Clear Entry
	// 0xda  Keypad Binary
	// 0xdb  Keypad Octal
	// 0xdc  Keypad Decimal
	// 0xdd  Keypad Hexadecimal

	#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control
	#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift
	#define KEY_LEFTALT 0xe2 // Keyboard Left Alt
	#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI
	#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control
	#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift
	#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt
	#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI

	// Media key bitflags
	#define KEY_MEDIA_PLAY 0x1
	#define KEY_MEDIA_PAUSE 0x2
	#define KEY_MEDIA_RECORD 0x4
	#define KEY_MEDIA_FASTFORWARD 0x8
	#define KEY_MEDIA_REWIND 0x10
	#define KEY_MEDIA_NEXTTRACK 0x20
	#define KEY_MEDIA_PREVIOUSTRACK 0x40
	#define KEY_MEDIA_STOP 0x80
	#define KEY_MEDIA_EJECT 0x100
	#define KEY_MEDIA_RANDOMPLAY 0x200
	#define KEY_MEDIA_REPEAT 0x400
	#define KEY_MEDIA_PLAYPAUSE 0x800
	#define KEY_MEDIA_MUTE 0x1000
	#define KEY_MEDIA_VOLUMEUP 0x2000
	#define KEY_MEDIA_VOLUMEDOWN 0x4000
	#define KEY_MEDIA_WWWHOME 0x8000
	#define KEY_MEDIA_MYCOMPUTER 0x10000
	#define KEY_MEDIA_CALCULATOR 0x20000
	#define KEY_MEDIA_WWWFAVORITES 0x40000
	#define KEY_MEDIA_WWWSEARCH 0x80000
	#define KEY_MEDIA_WWWSTOP 0x100000
	#define KEY_MEDIA_WWWBACK 0x200000
	#define KEY_MEDIA_MEDIASELECT 0x400000
	#define KEY_MEDIA_MAIL 0x800000

	// LED bitflags
	#define KEY_LED_NUMLOCK 0x1
	#define KEY_LED_CAPSLOCK 0x2
	#define KEY_LED_SCROLLLOCK 0x4
	#define KEY_LED_COMPOSE 0x8
	#define KEY_LED_KANA 0x10

	#endif // USB_HID_KEYS
	```
	```
	#ifndef ESP32_KEYBOARD_DEVICE_H
	#define ESP32_KEYBOARD_DEVICE_H

	#include "NimBLECharacteristic.h"
	#include <KeyboardHIDCodes.h>
	#include <KeyboardConfiguration.h>
	#include <BaseCompositeDevice.h>
	#include <Callback.h>
	#include <mutex>

	struct KeyboardInputReport {
		uint8_t modifiers = 0x00;
		uint8_t reserved = 0x00;
		uint8_t keys[6]; // 8 bits per key * 101 keys = 6 bytes
	};

	struct KeyboardMediaInputReport {
		uint32_t keys;
	};

	struct KeyboardOutputReport {
		bool numLockActive;
		bool capsLockActive;
		bool scrollLockActive;
		bool composeActive;
		bool kanaActive;

		constexpr KeyboardOutputReport(uint8_t value = 0) noexcept : 
			numLockActive(value & KEY_LED_NUMLOCK),
			capsLockActive(value & KEY_LED_CAPSLOCK),
			scrollLockActive(value & KEY_LED_SCROLLLOCK),
			composeActive(value & KEY_LED_COMPOSE),
			kanaActive(value & KEY_LED_KANA)
		{}
	};

	// Forwards 
	class KeyboardDevice;

	class KeyboardCallbacks : public NimBLECharacteristicCallbacks {
	public:
		KeyboardCallbacks(KeyboardDevice* device);

		void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
		void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
		void onStatus(NimBLECharacteristic* pCharacteristic, int code) override;
		void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override;

	private:
		KeyboardDevice* _device;
	};


	class KeyboardDevice : public BaseCompositeDevice {
	private:
		KeyboardConfiguration _config;
		NimBLECharacteristic* _input;
		NimBLECharacteristic* _mediaInput;
		NimBLECharacteristic* _output;

		KeyboardInputReport _inputReport;
		KeyboardMediaInputReport _mediaKeyInputReport;
		KeyboardCallbacks* _callbacks;

	public:
		KeyboardDevice();
		KeyboardDevice(const KeyboardConfiguration& config);
		~KeyboardDevice();
		
		void init(NimBLEHIDDevice* hid) override;
		const BaseCompositeDeviceConfiguration* getDeviceConfig() const override;

		void resetKeys();

		void keyPress(uint8_t keyCode);
		void keyRelease(uint8_t keyCode);
		void modifierKeyPress(uint8_t modifier);
		void modifierKeyRelease(uint8_t modifier);
		void mediaKeyPress(uint32_t mediaKey);
		void mediaKeyRelease(uint32_t mediaKey);

		Signal<KeyboardOutputReport> onLED;

		void sendKeyReport(bool defer = false);
		void sendMediaKeyReport(bool defer = false);

	private:
		void sendKeyReportImpl();
		void sendMediaKeyReportImpl();

		// Threading
		std::mutex _mutex;
	};

	#endif
	```
	```
	#ifndef KEYBOARDDESCRIPTORS_H
	#define KEYBOARDDESCRIPTORS_H

	#include <HIDTypes.h>

	#define KEYBOARD_REPORT_ID 0x40
	#define MEDIA_KEYS_REPORT_ID 0x43

	static const uint8_t _keyboardHIDReportDescriptor[] = {
	  // Input
	  USAGE_PAGE(1),      0x01,                     // USAGE_PAGE (Generic Desktop Ctrls)
	  USAGE(1),           0x06,                     // USAGE (Keyboard)
	  COLLECTION(1),      0x01,                     // COLLECTION (Application)
	  REPORT_ID(1),       KEYBOARD_REPORT_ID,       //   REPORT_ID (1)
	  USAGE_PAGE(1),      0x07,                     //   USAGE_PAGE (Kbrd/Keypad)
	  USAGE_MINIMUM(1),   0xE0,                     //   USAGE_MINIMUM (0xE0)
	  USAGE_MAXIMUM(1),   0xE7,                     //   USAGE_MAXIMUM (0xE7)
	  LOGICAL_MINIMUM(1), 0x00,                     //   LOGICAL_MINIMUM (0)
	  LOGICAL_MAXIMUM(1), 0x01,                     //   Logical Maximum (1)
	  REPORT_SIZE(1),     0x01,                     //   REPORT_SIZE (1)
	  REPORT_COUNT(1),    0x08,                     //   REPORT_COUNT (8)
	  HIDINPUT(1),        0x02,                     //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) ; Modifier byte
	  REPORT_COUNT(1),    0x01,                     //   REPORT_COUNT (1) ; 1 byte (Reserved)
	  REPORT_SIZE(1),     0x08,                     //   REPORT_SIZE (8)
	  HIDINPUT(1),        0x01,                     //   INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) ; Reserved byte
	  REPORT_COUNT(1),    0x06,                     //   REPORT_COUNT (6) ; 6 bytes (Keys)
	  REPORT_SIZE(1),     0x08,                     //   REPORT_SIZE(8)
	  LOGICAL_MINIMUM(1), 0x00,                     //   LOGICAL_MINIMUM(0)
	  LOGICAL_MAXIMUM(1), 0x65,                     //   LOGICAL_MAXIMUM(0x65) ; 101 keys
	  USAGE_PAGE(1),      0x07,                     //   USAGE_PAGE (Kbrd/Keypad)
	  USAGE_MINIMUM(1),   0x00,                     //   USAGE_MINIMUM (0)
	  USAGE_MAXIMUM(1),   0x65,                     //   USAGE_MAXIMUM (0x65)
	  HIDINPUT(1),        0x00,                     //   INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) ; Key arrays (6 bytes)
	  // Output
	  REPORT_COUNT(1),    0x05,                     //   REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
	  REPORT_SIZE(1),     0x01,                     //   REPORT_SIZE (1)
	  USAGE_PAGE(1),      0x08,                     //   USAGE_PAGE (LEDs)
	  USAGE_MINIMUM(1),   0x01,                     //   USAGE_MINIMUM (0x01) ; Num Lock
	  USAGE_MAXIMUM(1),   0x05,                     //   USAGE_MAXIMUM (0x05) ; Kana
	  HIDOUTPUT(1),       0x02,                     //   OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
	  REPORT_COUNT(1),    0x01,                     //   REPORT_COUNT (1) ; 3 bits (Padding)
	  REPORT_SIZE(1),     0x03,                     //   REPORT_SIZE (3)
	  HIDOUTPUT(1),       0x01,                     //   OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
	  END_COLLECTION(0),                            // END_COLLECTION
	};

	static const uint8_t _mediakeysHIDReportDescriptor[] = {
	  USAGE_PAGE(1),      0x0C,                         // USAGE_PAGE (Consumer)
	  USAGE(1),           0x01,                         // USAGE (Consumer Control)
	  COLLECTION(1),      0x01,                         // COLLECTION (Application)
	  REPORT_ID(1),       MEDIA_KEYS_REPORT_ID,         //   REPORT_ID (3)
	  USAGE_PAGE(1),      0x0C,                         //   USAGE_PAGE (Consumer)
	  LOGICAL_MINIMUM(1), 0x00,                         //   LOGICAL_MINIMUM (0)
	  LOGICAL_MAXIMUM(1), 0x01,                         //   LOGICAL_MAXIMUM (1)
	  REPORT_SIZE(1),     0x01,                         //   REPORT_SIZE (1)
	  REPORT_COUNT(1),    0x18,                         //   REPORT_COUNT (24)
	  USAGE(1),           0xB0,                         //   USAGE (Play)
	  USAGE(1),           0xB1,                         //   USAGE (Pause)
	  USAGE(1),           0xB2,                         //   USAGE (Record)
	  USAGE(1),           0xB3,                         //   USAGE (Fast Forward)
	  USAGE(1),           0xB4,                         //   USAGE (Rewind)
	  USAGE(1),           0xB5,                         //   USAGE (Scan Next Track)      
	  USAGE(1),           0xB6,                         //   USAGE (Scan Previous Track)  
	  USAGE(1),           0xB7,                         //   USAGE (Stop)                
	  USAGE(1),           0xB8,                         //   USAGE (Eject)    
	  USAGE(1),           0xB9,                         //   USAGE (Random Play)         
	  USAGE(1),           0xBC,                         //   USAGE (Repeat)
	  USAGE(1),           0xCD,                         //   USAGE (Play/Pause)          
	  USAGE(1),           0xE2,                         //   USAGE (Mute)                
	  USAGE(1),           0xE9,                         //   USAGE (Volume Increment)    
	  USAGE(1),           0xEA,                         //   USAGE (Volume Decrement)    
	  // 2 byte usages  
	  USAGE(2),           0x23, 0x02,                   //   Usage (WWW Home)
	  USAGE(2),           0x94, 0x01,                   //   Usage (My Computer) 
	  USAGE(2),           0x92, 0x01,                   //   Usage (Calculator)  
	  USAGE(2),           0x2A, 0x02,                   //   Usage (WWW fav)     
	  USAGE(2),           0x21, 0x02,                   //   Usage (WWW search)  
	  USAGE(2),           0x26, 0x02,                   //   Usage (WWW stop)    
	  USAGE(2),           0x24, 0x02,                   //   Usage (WWW back)    
	  USAGE(2),           0x83, 0x01,                   //   Usage (Media sel)   
	  USAGE(2),           0x8A, 0x01,                   //   Usage (Mail)        
	  HIDINPUT(1),        0x02,                         //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
	  END_COLLECTION(0)                                 // END_COLLECTION
	};


	#endif // KEYBOARDDESCRIPTORS_H
	```
	```
	#ifndef KEYBOARD_CONFIGURATION_H
	#define KEYBOARD_CONFIGURATION_H

	#include <BaseCompositeDevice.h>

	class KeyboardConfiguration : public BaseCompositeDeviceConfiguration
	{   
	public:
		KeyboardConfiguration();
		KeyboardConfiguration(uint8_t reportId);
		uint8_t getDeviceReportSize() const override;
		size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override;

		bool getUseMediaKeys() const;
		void setUseMediaKeys(bool value);
	private:
		bool _useMediaKeys;
	};

	#endif
	```
	```
	#ifndef ESP32_BLE_CONNECTION_STATUS_H
	#define ESP32_BLE_CONNECTION_STATUS_H
	#include "sdkconfig.h"
	#if defined(CONFIG_BT_ENABLED)

	#include "nimconfig.h"
	#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)

	#include <NimBLEServer.h>
	#include "NimBLECharacteristic.h"

	class BleConnectionStatus : public NimBLEServerCallbacks
	{
	public:
		BleConnectionStatus(void);
		void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override;
		void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override;
		bool isConnected();
	private:
		bool connected = false;
	};

	#endif // CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
	#endif // CONFIG_BT_ENABLED
	#endif // ESP32_BLE_CONNECTION_STATUS_H
	```
	```
	#ifndef ESP32_BLE_MULTI_HID_H
	#define ESP32_BLE_MULTI_HID_H
	#include "sdkconfig.h"
	#if defined(CONFIG_BT_ENABLED)

	#include "nimconfig.h"
	#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)

	#include "BleConnectionStatus.h"
	#include "NimBLEHIDDevice.h"
	#include "NimBLECharacteristic.h"

	#include "BLEHostConfiguration.h"
	#include "BaseCompositeDevice.h"

	#include <vector>
	#include "SafeQueue.hpp"

	class BleCompositeHID
	{
	public:
		BleCompositeHID(std::string deviceName = "ESP32 BLE Composite HID", std::string deviceManufacturer = "Espressif", uint8_t batteryLevel = 100);
		~BleCompositeHID();
		void begin();
		void begin(const BLEHostConfiguration& config);
		void end();

		void setBatteryLevel(uint8_t level);
		void addDevice(BaseCompositeDevice* device);
		bool isConnected();

		void queueDeviceDeferredReport(std::function<void()> && reportFunc);
		void sendDeferredReports();

		uint8_t batteryLevel;
		std::string deviceManufacturer;
		std::string deviceName;

	protected:
		virtual void onStarted(NimBLEServer *pServer){};

	private:
		static void taskServer(void *pvParameter);
		static void timedSendDeferredReports(void *pvParameter);

		BLEHostConfiguration _configuration;
		BleConnectionStatus* _connectionStatus;
		NimBLEHIDDevice* _hid;

		std::vector<BaseCompositeDevice*> _devices;
		SafeQueue<std::function<void()>> _deferredReports;
		TaskHandle_t _autoSendTaskHandle;
	};

	#endif // CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
	#endif // CONFIG_BT_ENABLED
	#endif // ESP32_BLE_MULTI_HID_H
	```
	```
	#ifndef COMPOSITE_CONFIG_H
	#define COMPOSITE_CONFIG_H

	#include <Arduino.h>
	//#include <HIDKeyboardTypes.h>
	#include <NimBLECharacteristic.h>
	#include <NimBLEHIDDevice.h>
	#include "BLEHostConfiguration.h"

	// Forwards
	class BleCompositeHID;

	class BaseCompositeDeviceConfiguration 
	{
	public:
		BaseCompositeDeviceConfiguration(uint8_t reportId);

		bool getAutoReport() const;
		void setAutoReport(bool value);

		uint8_t getReportId() const;
		void setHidReportId(uint8_t value);
		
		void setAutoDefer(bool value);
		bool getAutoDefer() const;

		virtual const char* getDeviceName() const;
		virtual BLEHostConfiguration getIdealHostConfiguration() const;
		virtual uint8_t getDeviceReportSize() const = 0;
		virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const = 0;

	private:
		bool _autoReport;
		bool _autoDefer;
		uint8_t _reportId;
	};


	class BaseCompositeDevice 
	{
		friend class BleCompositeHID;
	public:
		virtual void init(NimBLEHIDDevice* hid) = 0;
		virtual const BaseCompositeDeviceConfiguration* getDeviceConfig() const = 0;
		
		BleCompositeHID* getParent();

	protected:
		void queueDeferredReport(std::function<void()> && reportFunc);
		void setCharacteristics(NimBLECharacteristic* input, NimBLECharacteristic* output);
		NimBLECharacteristic* getInput();
		NimBLECharacteristic* getOutput();

	private:
		BleCompositeHID* _parent;
		NimBLECharacteristic* _input;
		NimBLECharacteristic* _output;
	};

	#endif
	```
	```
	#ifndef ESP32_BLE_HOST_CONFIG_H
	#define ESP32_BLE_HOST_CONFIG_H

	#include <Arduino.h>
	#include <string>

	// VID Source defines. These determine from which vendor pool the VID should be sourced from
	#define VENDOR_BLUETOOTH_SOURCE 0x01
	#define VENDOR_USB_SOURCE 0x02

	class BLEHostConfiguration
	{
	private:
		
		uint8_t _vidSource;
		uint16_t _vid;
		uint16_t _pid;
		uint16_t _guidVersion;
		std::string _modelNumber;
		std::string _softwareRevision;
		std::string _serialNumber;
		std::string _firmwareRevision;
		std::string _hardwareRevision;
		std::string _systemID;

	public:
		BLEHostConfiguration();
		uint16_t getVidSource();
		uint16_t getVid();
		uint16_t getPid();
		uint16_t getGuidVersion();
		const char* getModelNumber();
		const char* getSoftwareRevision();
		const char* getSerialNumber();
		const char* getFirmwareRevision();
		const char* getHardwareRevision();
		const char* getSystemID();

		void setVidSource(uint8_t value);
		void setVid(uint16_t value);
		void setPid(uint16_t value);
		void setGuidVersion(uint16_t value);
		void setModelNumber(const char *value);
		void setSoftwareRevision(const char *value);
		void setSerialNumber(const char *value);
		void setFirmwareRevision(const char *value);
		void setHardwareRevision(const char *value);

		// Set how quickly the auto-send should send queued reports. 
		// A value of 0 will send reports as soon as they are queued.
		void setQueueSendRate(uint32_t frequency);
		uint32_t getQueueSendRate() const;

		void setQueuedSending(bool value);
		bool getQueuedSending() const;

	private:
		uint32_t _deferSendRate;
		bool _threadedAutoSend;
	};

	#endif
	```
	codigos de ejemplos de la libreria:
	```
	#include <Arduino.h>
	#include <KeyboardDevice.h>
	#include <BleCompositeHID.h>

	BleCompositeHID compositeHID("ESP32 LED Keyboard", "Mystfit", 100);
	KeyboardDevice* keyboard;

	void OnLEDEvent(KeyboardOutputReport data){
		Serial.println(
		"LED Report: Number Lock: " + String(data.numLockActive) + 
		" Caps Lock: " + String(data.capsLockActive) + 
		" Scroll Lock: " + String(data.scrollLockActive) + 
		" Compose: " + String(data.composeActive) + 
		" Kana: " + String(data.kanaActive));
	}

	void setup()
	{
		Serial.begin(115200);

		keyboard = new KeyboardDevice();
		FunctionSlot<KeyboardOutputReport> OnLEDEventSlot(OnLEDEvent);
		keyboard->onLED.attach(OnLEDEventSlot);

		compositeHID.addDevice(keyboard);
		compositeHID.begin();

		Serial.println("Waiting for connection");
		delay(3000);
	}

	void loop()
	{
		keyboard->keyPress(KEY_NUMLOCK);
		delay(10);
		keyboard->keyRelease(KEY_NUMLOCK);
		delay(1000);

		keyboard->keyPress(KEY_CAPSLOCK);
		delay(10);
		keyboard->keyRelease(KEY_CAPSLOCK);
		delay(1000);

		keyboard->keyPress(KEY_SCROLLLOCK);
		delay(10);
		keyboard->keyRelease(KEY_SCROLLLOCK);
		delay(1000);

		keyboard->keyPress(KEY_COMPOSE);
		delay(10);
		keyboard->keyRelease(KEY_COMPOSE);
		delay(1000);
	}
	```
	```
	#include <Arduino.h>
	#include <KeyboardDevice.h>
	#include <BleCompositeHID.h>

	BleCompositeHID compositeHID("ESP32 LED Keyboard", "Mystfit", 100);
	KeyboardDevice* keyboard;

	void OnLEDEvent(KeyboardOutputReport data){
		Serial.println(
		"LED Report: Number Lock: " + String(data.numLockActive) + 
		" Caps Lock: " + String(data.capsLockActive) + 
		" Scroll Lock: " + String(data.scrollLockActive) + 
		" Compose: " + String(data.composeActive) + 
		" Kana: " + String(data.kanaActive));
	}

	void setup()
	{
		Serial.begin(115200);

		keyboard = new KeyboardDevice();
		FunctionSlot<KeyboardOutputReport> OnLEDEventSlot(OnLEDEvent);
		keyboard->onLED.attach(OnLEDEventSlot);

		compositeHID.addDevice(keyboard);
		compositeHID.begin();

		Serial.println("Waiting for connection");
		delay(3000);
	}

	void loop()
	{
		keyboard->keyPress(KEY_NUMLOCK);
		delay(10);
		keyboard->keyRelease(KEY_NUMLOCK);
		delay(1000);

		keyboard->keyPress(KEY_CAPSLOCK);
		delay(10);
		keyboard->keyRelease(KEY_CAPSLOCK);
		delay(1000);

		keyboard->keyPress(KEY_SCROLLLOCK);
		delay(10);
		keyboard->keyRelease(KEY_SCROLLLOCK);
		delay(1000);

		keyboard->keyPress(KEY_COMPOSE);
		delay(10);
		keyboard->keyRelease(KEY_COMPOSE);
		delay(1000);
	}
	```
	```
	#include <Arduino.h>
	#include <KeyboardDevice.h>
	#include <BleCompositeHID.h>

	#define KEY_RANGE_MIN KEY_A
	#define KEY_RANGE_MAX KEY_RIGHTMETA

	#define MODKEY_RANGE_MIN KEY_MOD_LALT
	#define MODKEY_RANGE_MAX KEY_MOD_RMETA

	BleCompositeHID compositeHID("ESP32 Keyboard", "Mystfit", 100);
	KeyboardDevice* keyboard;

	void setup()
	{
		Serial.begin(115200);

		keyboard = new KeyboardDevice();
		compositeHID.addDevice(keyboard);
		compositeHID.begin();

		Serial.println("Waiting for connection");
		delay(3000);
	}

	void loop()
	{
		if (compositeHID.isConnected())
		{
			uint8_t modKey = 0x01;
			for(uint8_t modKeyCount = 0; modKeyCount < 8; modKeyCount++)
			{
				Serial.println("Pressing modifier key " + String(modKey));
				keyboard->modifierKeyPress(modKey);
				delay(10);
				keyboard->modifierKeyRelease(modKey);
				delay(1000);
				modKey = modKey << 1;
			}

			for(uint8_t keyCode = KEY_RANGE_MIN; keyCode <= KEY_RANGE_MAX; keyCode++)
			{
				Serial.println("Pressing key " + String(keyCode));
				keyboard->keyPress(keyCode);
				delay(10);
				keyboard->keyRelease(keyCode);
				delay(1000);
			}
		}
	}
	```