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.
```
Here are some suggestions for improving your code:

**Organization and Structure**

*   The code is quite lengthy and complex. Consider breaking it down into smaller files or modules, each responsible for a specific functionality. This will make it easier to maintain and understand.
*   Use a more consistent naming convention throughout the code. For example, some functions use camelCase, while others use underscore notation.

**Performance and Efficiency**

*   In the `executeMatchingGesture` function, you're iterating over all stored gestures to find a match. This could become inefficient if you have a large number of gestures. Consider implementing a more efficient search algorithm, such as a hash table or binary search.
*   In the `appendToBuffer` function, you're using a circular buffer implementation. While this is a good approach, you might want to consider using a more robust data structure, such as a queue or deque, which can provide better performance and safety guarantees.

**Error Handling and Debugging**

*   Throughout the code, there are several places where errors are handled by simply printing an error message and continuing execution. Consider implementing more robust error handling mechanisms, such as returning error codes or throwing exceptions.
*   Add more debug logging statements to help diagnose issues. This will make it easier to identify problems when they arise.

**Code Style and Conventions**

*   Some functions have very long parameter lists. Consider breaking these up into smaller functions or using structs/classes to encapsulate related data.
*   There are several places where magic numbers are used (e.g., `INACTIVITY_TIMEOUT_MS`, `TOUCH_BUFFER_SIZE`). Define these values as constants at the top of the file to make the code more readable and maintainable.

**Security**

*   When reading user input from the serial console, consider adding input validation and sanitization to prevent potential security vulnerabilities.

**Additional Ideas**

*   Consider implementing a gesture editing feature, allowing users to modify existing gestures or create new ones.
*   You could also explore integrating machine learning algorithms to improve gesture recognition accuracy.
*   Think about adding support for multiple keyboard layouts or languages.

Overall, your code provides a solid foundation for a gesture-based keyboard system. By addressing these areas, you can further improve its performance, maintainability, and overall quality.
```
THIS IS MY CODE:
```
  // ========================
  // ESP32 HID Controller - Enhanced Refactored Version
  // ========================

  /**
  * @file HIDController.ino
  * @brief Enhanced professional-grade code for 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.
  */

  #include <Arduino.h>
  #include <BleCompositeHID.h>
  #include <KeyboardDevice.h>
  #include <Preferences.h>
  #include <KeyboardHIDCodes.h>

  // ========================
  // Configuration Constants
  // ========================

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

  // Touch Pin Configuration
  constexpr uint8_t TOUCH_PINS[] PROGMEM = {4, 12, 13, 14, 15, 27, 32, 33};
  constexpr uint8_t TOUCH_THRESHOLDS[] PROGMEM = {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;

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

  // ========================
  // Enumerations
  // ========================

  /**
  * @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
  };

  // ========================
  // Structures
  // ========================

  /**
  * @struct Action
  * @brief Represents an individual HID action.
  */
  #pragma pack(push, 1)
  struct Action {
      ActionType type;     ///< Type of action
      union {
          uint8_t parameter; ///< HID code for NORMAL_KEY, MEDIA_KEY, etc.
          char text[16];     ///< Associated text (only for TEXT type)
      };

      Action() : type(ActionType::NORMAL_KEY), parameter(0) {}
  };
  #pragma pack(pop)

  /**
  * @struct Gesture
  * @brief Represents a user-defined gesture.
  */
  struct Gesture {
      uint8_t id;                                ///< Unique identifier for the gesture
      uint8_t length;                            ///< Length of the pin sequence
      uint8_t actionCount;                       ///< Number of actions in the gesture
      uint8_t pinSequence[TOUCH_BUFFER_SIZE];    ///< Sequence of touch pins
      Action actions[TOUCH_BUFFER_SIZE];         ///< Actions associated with the gesture

      Gesture() : id(0), length(0), actionCount(0) {
          memset(pinSequence, 0, sizeof(pinSequence));
          memset(actions, 0, sizeof(actions));
      }
  };

  /**
  * @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},
      {"F13", KEY_F13}, {"F14", KEY_F14}, {"F15", KEY_F15}, {"F16", KEY_F16},
      {"F17", KEY_F17}, {"F18", KEY_F18}, {"F19", KEY_F19}, {"F20", KEY_F20},
      {"F21", KEY_F21}, {"F22", KEY_F22}, {"F23", KEY_F23}, {"F24", KEY_F24},

      // 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 char* action) {
      if (action == nullptr) return 0;

      size_t actionLen = strlen(action);
      if (actionLen == 0) return 0;

      // Temporary buffer for case-insensitive comparison
      char tempAction[32];
      strncpy(tempAction, action, sizeof(tempAction) - 1);
      tempAction[sizeof(tempAction) - 1] = '\0';

      // Convert to uppercase for comparison
      for (size_t i = 0; i < strlen(tempAction); i++) {
          tempAction[i] = toupper(tempAction[i]);
      }

      for (size_t i = 0; i < HIDMapSize; i++) {
          HIDPair pair;
          memcpy_P(&pair, &HIDMap[i], sizeof(pair));
          if (strcasecmp(tempAction, pair.key) == 0) {
              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[] PROGMEM = "!@#$%^&*()_+{}|:\"<>?~";
      for (uint8_t i = 0; i < sizeof(symbolsRequiringShift) - 1; i++) {
          if (c == pgm_read_byte(&symbolsRequiringShift[i])) return true;
      }
      return false;
  }

  // ========================
  // GestureManager Class
  // ========================

  /**
  * @class GestureManager
  * @brief Manages gestures including loading, adding, deleting, and listing.
  */
  class GestureManager {
  public:
      /**
      * @brief Constructor for GestureManager.
      * @param prefs Reference to the Preferences instance.
      */
      GestureManager(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) {
          char idKey[10];
          snprintf(idKey, sizeof(idKey), "g%u_id", index);
          if (!preferences.isKey(idKey)) return false;

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

          // Load pin sequence
          char seqKey[10];
          snprintf(seqKey, sizeof(seqKey), "g%u_seq", index);
          String sequenceStr = preferences.getString(seqKey, "");
          gesture.length = 0;
          int start = 0;
          while (start < sequenceStr.length() && gesture.length < TOUCH_BUFFER_SIZE) {
              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[gesture.length++] = pin;
              }

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

          // Load actions
          char actKey[10];
          snprintf(actKey, sizeof(actKey), "g%u_acts", index);
          String actionsStr = preferences.getString(actKey, "");
          gesture.actionCount = 0;
          start = 0;
          while (start < actionsStr.length() && gesture.actionCount < TOUCH_BUFFER_SIZE) {
              int semicolon = actionsStr.indexOf(';', start);
              String actionStr = (semicolon == -1) ? actionsStr.substring(start) : actionsStr.substring(start, semicolon);
              actionStr.trim();

              if (actionStr.length() > 0) {
                  Action action;
                  action.type = ActionType::NORMAL_KEY;
                  memset(action.text, 0, sizeof(action.text));

                  // Attempt to convert to HID code
                  uint8_t hidCode = getHIDCode(actionStr.c_str());

                  if (hidCode != 0) {
                      // Determine action type based on HID code ranges
                      if (hidCode >= 0xB0 && hidCode <= 0xF6) {
                          action.type = ActionType::MEDIA_KEY;
                          action.parameter = 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 = hidCode;
                      }
                      else {
                          action.type = ActionType::NORMAL_KEY;
                          action.parameter = hidCode;
                      }
                  }
                  else {
                      // Assume it's text
                      action.type = ActionType::TEXT;
                      strncpy(action.text, actionStr.c_str(), sizeof(action.text) - 1);
                      action.text[sizeof(action.text) - 1] = '\0';
                  }

                  // Add action to gesture
                  gesture.actions[gesture.actionCount++] = action;
              }

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

          // Additional validations
          return (gesture.length > 0) && (gesture.actionCount > 0);
      }

      /**
      * @brief Compares two sequences to determine if they match.
      * @param a First sequence.
      * @param aLen Length of the first sequence.
      * @param b Second sequence.
      * @param bLen Length of the second sequence.
      * @return True if sequences match, false otherwise.
      */
      bool compareSequences(const uint8_t* a, uint8_t aLen, const uint8_t* b, uint8_t bLen) const {
          if (aLen < bLen) {
              return false;
          }

          // Compare the last bLen elements of 'a' with 'b'
          uint8_t startA = (aLen > bLen) ? (aLen - bLen) : 0;
          for (uint8_t i = 0; i < bLen; i++) {
              if (a[startA + i] != b[i]) {
                  return false;
              }
          }
          return true;
      }

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

      /**
      * @brief Adds a new gesture to preferences storage.
      * @param newGesture Gesture to add.
      * @return True if added successfully, false otherwise.
      */
      bool addGesture(const Gesture& newGesture) {
          if (newGesture.id == 0) return false;

          uint8_t currentCount = preferences.getUInt("count", 0);
          if (currentCount >= MAX_GESTURES) { // Limit to MAX_GESTURES gestures
              Serial.println(F(LOG_PREFIX "Maximum number of gestures reached."));
              return false;
          }

          // Save ID
          char idKey[10];
          snprintf(idKey, sizeof(idKey), "g%u_id", currentCount);
          if (!preferences.putUInt(idKey, newGesture.id)) {
              Serial.println(F(LOG_PREFIX "Failed to save gesture ID."));
              return false;
          }

          // Save pin sequence
          char seqKey[10];
          snprintf(seqKey, sizeof(seqKey), "g%u_seq", currentCount);
          String seqBuffer = "";
          for (uint8_t j = 0; j < newGesture.length; j++) {
              seqBuffer += String(newGesture.pinSequence[j]);
              if (j < newGesture.length - 1) {
                  seqBuffer += ",";
              }
          }
          if (!preferences.putString(seqKey, seqBuffer)) {
              Serial.println(F(LOG_PREFIX "Failed to save gesture pin sequence."));
              return false;
          }

          // Save actions
          char actKey[10];
          snprintf(actKey, sizeof(actKey), "g%u_acts", currentCount);
          String actsBuffer = "";
          for (uint8_t a = 0; a < newGesture.actionCount; a++) {
              if (a > 0) actsBuffer += ";";
              if (newGesture.actions[a].type == ActionType::TEXT) {
                  actsBuffer += String(newGesture.actions[a].text);
              }
              else {
                  actsBuffer += String(newGesture.actions[a].parameter, HEX); // Save in hexadecimal for clarity
              }
          }
          if (!preferences.putString(actKey, actsBuffer)) {
              Serial.println(F(LOG_PREFIX "Failed to save gesture actions."));
              return false;
          }

          // Update gesture count
          if (!preferences.putUInt("count", currentCount + 1)) {
              Serial.println(F(LOG_PREFIX "Failed to update gesture count."));
              return false;
          }

          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 = preferences.getUInt("count", 0);
          bool found = false;

          for (uint8_t i = 0; i < gestureCount; i++) {
              char idKey[10];
              snprintf(idKey, sizeof(idKey), "g%u_id", i);
              uint8_t storedId = static_cast<uint8_t>(preferences.getUInt(idKey, 0));
              if (storedId == id) {
                  // Shift all subsequent gestures up
                  for (uint8_t j = i; j < gestureCount - 1; j++) {
                      char srcIdKey[10], srcSeqKey[10], srcActKey[10];
                      snprintf(srcIdKey, sizeof(srcIdKey), "g%u_id", j + 1);
                      snprintf(srcSeqKey, sizeof(srcSeqKey), "g%u_seq", j + 1);
                      snprintf(srcActKey, sizeof(srcActKey), "g%u_acts", j + 1);

                      char dstIdKey[10], dstSeqKey[10], dstActKey[10];
                      snprintf(dstIdKey, sizeof(dstIdKey), "g%u_id", j);
                      snprintf(dstSeqKey, sizeof(dstSeqKey), "g%u_seq", j);
                      snprintf(dstActKey, sizeof(dstActKey), "g%u_acts", j);

                      // Copy data
                      preferences.putUInt(dstIdKey, preferences.getUInt(srcIdKey, 0));
                      preferences.putString(dstSeqKey, preferences.getString(srcSeqKey, ""));
                      preferences.putString(dstActKey, preferences.getString(srcActKey, ""));

                      // Remove source
                      preferences.remove(srcIdKey);
                      preferences.remove(srcSeqKey);
                      preferences.remove(srcActKey);
                  }

                  // Remove the last gesture
                  char lastIdKey[10], lastSeqKey[10], lastActKey[10];
                  snprintf(lastIdKey, sizeof(lastIdKey), "g%u_id", gestureCount - 1);
                  snprintf(lastSeqKey, sizeof(lastSeqKey), "g%u_seq", gestureCount - 1);
                  snprintf(lastActKey, sizeof(lastActKey), "g%u_acts", gestureCount - 1);
                  preferences.remove(lastIdKey);
                  preferences.remove(lastSeqKey);
                  preferences.remove(lastActKey);

                  // Update gesture count
                  if (!preferences.putUInt("count", gestureCount - 1)) {
                      Serial.println(F(LOG_PREFIX "Failed to update gesture count after deletion."));
                      return false;
                  }

                  Serial.println(F(LOG_PREFIX "Gesture deleted successfully."));
                  found = true;
                  break;
              }
          }

          if (!found) {
              Serial.println(F(LOG_PREFIX "Gesture not found."));
          }

          return found;
      }

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

      /**
      * @brief Lists all stored gestures via Serial.
      */
      void listGestures() const {
          uint8_t gestureCount = 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++) {
              // Use mutable cast to call loadGesture
              if (!const_cast<GestureManager*>(this)->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 (uint8_t j = 0; j < gesture.length; j++) {
                  Serial.print(gesture.pinSequence[j]);
                  if (j < gesture.length - 1) {
                      Serial.print(F(" -> "));
                  }
              }

              Serial.print(F(" | Actions: "));
              for (uint8_t a = 0; a < gesture.actionCount; a++) {
                  char actionType[30];
                  switch (gesture.actions[a].type) {
                      case ActionType::NORMAL_KEY: strcpy(actionType, "Key"); break;
                      case ActionType::MEDIA_KEY: strcpy(actionType, "Media Key"); break;
                      case ActionType::MODIFIER_PRESS: strcpy(actionType, "Modifier (Press)"); break;
                      case ActionType::MODIFIER_RELEASE: strcpy(actionType, "Modifier (Release)"); break;
                      case ActionType::TEXT: strcpy(actionType, "Text"); break;
                      default: strcpy(actionType, "Unknown"); break;
                  }
                  if (gesture.actions[a].type != ActionType::TEXT) {
                      char actionMsg[50];
                      snprintf(actionMsg, sizeof(actionMsg), "%s(0x%02X)", actionType, gesture.actions[a].parameter);
                      Serial.print(actionMsg);
                  }
                  else {
                      char actionMsg[50];
                      snprintf(actionMsg, sizeof(actionMsg), "%s(\"%s\")", actionType, gesture.actions[a].text);
                      Serial.print(actionMsg);
                  }
                  if (a < gesture.actionCount - 1) {
                      Serial.print(F(", "));
                  }
              }
              Serial.println(); // New line after each gesture
          }
      }

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

  // ========================
  // KeyboardManager Class
  // ========================

  /**
  * @class KeyboardManager
  * @brief Manages keyboard actions based on gestures.
  */
  class KeyboardManager {
  public:
      /**
      * @brief Constructor for KeyboardManager.
      * @param kbDevice Pointer to the KeyboardDevice instance.
      */
      KeyboardManager(KeyboardDevice* kbDevice) : keyboard(kbDevice) {}

      /**
      * @brief Executes the actions defined in a gesture.
      * @param gesture Gesture containing the actions to execute.
      */
      void executeGestureActions(const Gesture& gesture) {
          Serial.print(F(LOG_PREFIX "Executing actions for gesture ID: "));
          Serial.println(gesture.id);
          for (uint8_t a = 0; a < gesture.actionCount; a++) {
              const Action& action = gesture.actions[a];
              switch (action.type) {
                  case ActionType::NORMAL_KEY:
                      executeNormalKey(action.parameter);
                      break;
                  case ActionType::MEDIA_KEY:
                      executeMediaKey(action.parameter);
                      break;
                  case ActionType::MODIFIER_PRESS:
                      executeModifierPress(action.parameter);
                      break;
                  case ActionType::MODIFIER_RELEASE:
                      executeModifierRelease(action.parameter);
                      break;
                  case ActionType::TEXT:
                      executeText(action.text);
                      break;
                  default:
                      Serial.println(F(LOG_PREFIX "Unknown action type."));
                      break;
              }
          }
      }

      /**
      * @brief Sends a string of text as keyboard input.
      * @param text Text to send.
      */
      void executeText(const char* text) {
          Serial.print(F(LOG_PREFIX "Sending text: "));
          Serial.println(text);
          for (int c = 0; text[c] != '\0'; c++) {
              char currentChar = text[c];
              bool shiftNeeded = needsShift(currentChar);
              uint8_t key = getHIDCode(&currentChar);

              if (shiftNeeded) {
                  keyboard->modifierKeyPress(KEY_LEFTSHIFT);
                  Serial.println(F("Shift pressed."));
              }

              if (key != 0) {
                  keyboard->keyPress(key);
                  delay(50); // Increased delay for reliability
                  keyboard->keyRelease(key);
                  delay(50);
                  Serial.print(F("Key sent: "));
                  Serial.println(currentChar);
              }
              else {
                  Serial.print(F(LOG_PREFIX "Failed to get HID code for character: "));
                  Serial.println(currentChar);
              }

              if (shiftNeeded) {
                  keyboard->modifierKeyRelease(KEY_LEFTSHIFT);
                  delay(50);
                  Serial.println(F("Shift released."));
              }

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

  private:
      KeyboardDevice* keyboard; ///< Pointer to the KeyboardDevice

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

      /**
      * @brief Executes a media key press and release.
      * @param mediaKey HID media key code to press.
      */
      void executeMediaKey(uint8_t mediaKey) {
          Serial.print(F(LOG_PREFIX "Pressing media key code: 0x"));
          Serial.println(mediaKey, HEX);
          keyboard->mediaKeyPress(mediaKey);
          keyboard->mediaKeyRelease(mediaKey);
      }

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

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

      /**
      * @brief Retrieves the HID code for a given character.
      * @param c Pointer to the character.
      * @return HID code.
      */
      uint8_t getHIDCode(char* c) {
          char chStr[2] = {*c, '\0'};
          return ::getHIDCode(chStr);
      }
  };

  // ========================
  // SerialCommandHandler Class
  // ========================

  /**
  * @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()) {  // Handle multiple commands at once
              char commandBuffer[256];
              uint16_t len = 0;

              // Read until '\n' or buffer is full
              while (Serial.available() && len < sizeof(commandBuffer) - 1) {
                  char c = Serial.read();
                  if (c == '\n') break;
                  commandBuffer[len++] = c;
              }
              commandBuffer[len] = '\0';

              // Process command
              char* command = strtok(commandBuffer, " ");
              if (command == NULL) {
                  logMessage(F("Empty command."));
                  return;
              }

              if (strcasecmp(command, "ADD") == 0) {
                  // Expected format: ADD <pin_sequence>|<actions>
                  char* params = strtok(NULL, "\n");
                  if (params == NULL) {
                      logMessage(F("Invalid ADD format. Usage: ADD <pin_sequence>|<actions>"));
                      return;
                  }

                  addCommandGesture(params);
              }
              else if (strcasecmp(command, "DEL") == 0) {
                  // Expected format: DEL <ID>
                  char* idStr = strtok(NULL, " ");
                  if (idStr == NULL) {
                      logMessage(F("Invalid DEL format. Usage: DEL <ID>"));
                      return;
                  }
                  delCommandGesture(idStr);
              }
              else if (strcasecmp(command, "LIST") == 0) {
                  // List all stored gestures
                  gestureManager->listGestures();
              }
              else if (strcasecmp(command, "TEST") == 0) {
                  // Test command to check available memory
                  char bufferMsg[50];
                  snprintf(bufferMsg, sizeof(bufferMsg), "Free memory: %u bytes", ESP.getFreeHeap());
                  logMessage(bufferMsg);
              }
              else {
                  logMessage(F("Unknown command."));
              }
          }
      }

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

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

      void logMessage(const char* 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(char* params) {
          char* delimiter = strchr(params, '|');
          if (delimiter == NULL) {
              logMessage(F("Invalid ADD format. Missing '|'. Usage: ADD <pin_sequence>|<actions>"));
              return;
          }

          *delimiter = '\0';
          char* sequenceStr = params;
          char* actionsStr = delimiter + 1;

          // Create new gesture
          Gesture newGesture;
          newGesture.id = gestureManager->generateID();

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

          // Process pin sequence
          char* pinToken = strtok(sequenceStr, " ");
          while (pinToken != NULL && newGesture.length < TOUCH_BUFFER_SIZE) {
              uint8_t pin = static_cast<uint8_t>(atoi(pinToken));
              // Validate pin
              bool validPin = false;
              for (uint8_t j = 0; j < NUM_TOUCH_PINS; j++) {
                  uint8_t touchPin = pgm_read_byte(&TOUCH_PINS[j]);
                  if (pin == touchPin) {
                      newGesture.pinSequence[newGesture.length++] = pin;
                      validPin = true;
                      break;
                  }
              }
              if (!validPin) {
                  logMessage(F("Invalid pin ignored in sequence."));
              }
              pinToken = strtok(NULL, " ");
          }

          if (newGesture.length == 0) {
              logMessage(F("No valid pins added to gesture."));
              return;
          }

          // Process actions using ';' as delimiter to support multi-word actions
          char* actionToken = strtok(actionsStr, ";");
          while (actionToken != NULL && newGesture.actionCount < TOUCH_BUFFER_SIZE) {
              Action action;
              action.type = ActionType::NORMAL_KEY;
              memset(action.text, 0, sizeof(action.text));

              // Convert action token to HID code
              uint8_t hidCode = getHIDCode(actionToken);

              if (hidCode != 0) {
                  // Determine action type based on HID code ranges
                  if (hidCode >= 0xB0 && hidCode <= 0xF6) {
                      action.type = ActionType::MEDIA_KEY;
                      action.parameter = 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 = hidCode;
                  }
                  else {
                      action.type = ActionType::NORMAL_KEY;
                      action.parameter = hidCode;
                  }
              }
              else {
                  // Assume it's text
                  action.type = ActionType::TEXT;
                  strncpy(action.text, actionToken, sizeof(action.text) - 1);
                  action.text[sizeof(action.text) - 1] = '\0';
              }

              newGesture.actions[newGesture.actionCount++] = action;
              actionToken = strtok(NULL, ";");
          }

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

          // Add gesture to preferences
          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(char* idStr) {
          uint8_t id = static_cast<uint8_t>(atoi(idStr));
          if (!gestureManager->deleteGesture(id)) {
              logMessage(F("Gesture not found."));
          }
      }
  };

  // ========================
  // TouchHandler Class
  // ========================

  /**
  * @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 = pgm_read_byte(&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 pinDetected Pin number that was touched.
      * @param touched Boolean flag indicating if a touch was detected.
      */
      void detectTouch(uint8_t& pinDetected, bool& touched) {
          touched = false;
          pinDetected = 0;

          for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
              uint8_t pin = pgm_read_byte(&TOUCH_PINS[i]);
              uint8_t threshold = pgm_read_byte(&TOUCH_THRESHOLDS[i]);
              bool isTouched = touchRead(pin) < threshold;

              // Detect change from not touched to touched
              if (isTouched && !(previousTouchState & (1 << i))) {
                  pinDetected = pin;
                  touched = true;
                  previousTouchState |= (1 << i); // Mark as touched
                  break; // Handle one touch at a time
              }
              else if (!isTouched && (previousTouchState & (1 << i))) {
                  previousTouchState &= ~(1 << i); // Mark as not touched
              }
          }

          // Control LED based on recent activity
          if (touched) {
              setLEDState(true);
              lastActivityTime = millis();
          }
          else {
              // Turn off LED if inactivity exceeds a short threshold
              if (millis() - lastActivityTime > LED_INACTIVITY_THRESHOLD_MS) {
                  setLEDState(false);
              }
          }
      }

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

      // LED inactivity threshold in milliseconds
      const unsigned long LED_INACTIVITY_THRESHOLD_MS = 200;

      /**
      * @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);
      }
  };

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

  Preferences preferencesInstance;                        ///< Preferences storage
  GestureManager gestureManager(preferencesInstance);     ///< Gesture manager
  KeyboardDevice* keyboard = nullptr;                     ///< Pointer to KeyboardDevice
  KeyboardManager keyboardManager(nullptr);               ///< Keyboard manager (initialized later)
  SerialCommandHandler serialHandler(&gestureManager);     ///< Serial command handler
  TouchHandler touchHandler;                              ///< Touch handler

  // Touch buffer and timing variables
  uint8_t touchBufferGlobal[TOUCH_BUFFER_SIZE];
  uint16_t bufferStart = 0;
  uint16_t bufferSize = 0;
  unsigned long lastTouchTime = 0;

  // BLE and Keyboard Devices
  BleCompositeHID compositeHID(DEVICE_NAME, DEVICE_MANUFACTURER, 50); ///< BLE composite HID

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

  /**
  * @brief Adds a pin to the global touch buffer.
  * @param pin Pin number to add.
  */
  void appendToBuffer(uint8_t pin) {
      if (bufferSize < TOUCH_BUFFER_SIZE) {
          touchBufferGlobal[(bufferStart + bufferSize) % TOUCH_BUFFER_SIZE] = pin;
          bufferSize++;
      }
      else {
          // If buffer is full, overwrite the oldest entry
          bufferStart = (bufferStart + 1) % TOUCH_BUFFER_SIZE;
          touchBufferGlobal[(bufferStart + bufferSize - 1) % TOUCH_BUFFER_SIZE] = pin;
      }
  }

  /**
  * @brief Clears the global touch buffer.
  */
  void clearBuffer() {
      bufferStart = 0;
      bufferSize = 0;
      Serial.println(F(LOG_PREFIX "Touch buffer cleared."));
  }

  /**
  * @brief Executes a matching gesture based on the current touch buffer.
  */
  void executeMatchingGesture() {
      Serial.println(F(LOG_PREFIX "Verifying gesture..."));
      uint8_t gestureCount = gestureManager.getGestureCount();
      if (gestureCount == 0) {
          Serial.println(F(LOG_PREFIX "No gestures stored."));
      }
      else {
          Gesture gesture;
          bool gestureFound = false;
          for (uint8_t i = 0; i < gestureCount; ++i) {
              if (!gestureManager.loadGesture(i, gesture)) {
                  Serial.println(F(LOG_PREFIX "Failed to load gesture."));
                  continue;
              }

              if (gestureManager.compareSequences(touchBufferGlobal + bufferStart, bufferSize, gesture.pinSequence, gesture.length)) {
                  Serial.print(F(LOG_PREFIX "Gesture found with ID: "));
                  Serial.println(gesture.id);

                  if (!compositeHID.isConnected() || keyboard == nullptr) {
                      Serial.println(F(LOG_PREFIX "Device not connected or keyboard not initialized."));
                      continue;
                  }

                  keyboardManager.executeGestureActions(gesture);
                  Serial.println(F(LOG_PREFIX "Gesture actions executed."));
                  gestureFound = true;
                  // Do not break to allow multiple gestures to be recognized in succession
              }
          }
          if (!gestureFound) {
              Serial.println(F(LOG_PREFIX "No matching gesture found."));
          }
      }
      clearBuffer();
      // lastGestureTime is no longer used; remove its reference
  }

  // ========================
  // 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
      }

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

      // Initialize Keyboard Device
      KeyboardConfiguration keyboardConfig;
      keyboardConfig.setAutoReport(true);
      keyboard = new KeyboardDevice(keyboardConfig);
      if (keyboard == nullptr) {
          Serial.println(F(LOG_PREFIX "Failed to allocate KeyboardDevice."));
          // Handle error appropriately
      }
      compositeHID.addDevice(keyboard);

      compositeHID.begin();

      // Initialize Touch Handler
      touchHandler.initialize();

      // Initialize Keyboard Manager with the Keyboard Device
      keyboardManager = KeyboardManager(keyboard);

      // 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() {
      uint8_t touchedPin = 0;
      bool touched = false;
      touchHandler.detectTouch(touchedPin, touched);

      unsigned long currentTime = millis();

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

      // If inactivity exceeds timeout, verify gestures
      if (bufferSize > 0 && (currentTime - lastTouchTime > INACTIVITY_TIMEOUT_MS)) {
          executeMatchingGesture();
      }

      // Handle serial commands
      if (Serial.available()) {
          serialHandler.handleCommands();
      }

      // Optional: Implement periodic tasks or additional functionalities here
  }
```
	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);
			}
		}
	}
	```