necesito ayuda, antes mi codigo con el gesto "ADD 32 33|HELLO" me enviaba a la pagina de linkedin, pero luego de intentar solucionarlo el problema unicamente empeoro, ahora parece abrir una cantidad aleatoria de pestañas de linkedin, y una para iniciar sesion en outlook.

CODIGO DEL PROYECTO:
```
// ========================
// Proyecto ESP32 HID Profesional
// ========================

// ========================
// Inclusión de Librerías y Definiciones
// ========================
  #include <Arduino.h>
  #include <BleCompositeHID.h>
  #include <KeyboardDevice.h>
  #include <Preferences.h>
  #include <KeyboardHIDCodes.h>

// ========================
// Definición de Enumeraciones y Constantes
// ========================

  enum ActionType {
    NORMAL_KEY = 0,
    MEDIA_KEY,
    MODIFIER_PRESS,
    MODIFIER_RELEASE,
    TEXT
  };

  // Prefijos para evitar conflictos
  const uint8_t MY_KEY_LEFTGUI PROGMEM = KEY_LEFTMETA;
  const uint8_t MY_KEY_RIGHTGUI PROGMEM = KEY_RIGHTMETA;
  // ... [Otros códigos HID pueden añadirse aquí si es necesario]

  // Pines Táctiles
  const uint8_t TOUCH_PINS[] PROGMEM = { 4, 12, 13, 14, 15, 27, 32, 33 };
  const 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]);

  // Parámetros de Gestos
  constexpr uint16_t INACTIVITY_TIMEOUT_MS = 500;  // Tiempo en ms
  constexpr uint8_t TOUCH_BUFFER_SIZE = 100;

  // Buffers y Contadores
  uint8_t touchBuffer[TOUCH_BUFFER_SIZE];
  uint16_t bufferStart = 0;
  uint16_t bufferSize = 0;

// ========================
// Estructuras y Clases
// ========================
  #pragma pack(push, 1)
  struct Action {
    ActionType type;  // Tipo de acción
    union {
      uint8_t parameter;  // Código HID para tipos NORMAL_KEY, MEDIA_KEY, etc.
      char text[16];      // Texto asociado (solo para tipo TEXT)
    };
  };

  struct Gesture {
    uint8_t id;
    uint8_t length;
    uint8_t actionCount;
    uint8_t pinSequence[TOUCH_BUFFER_SIZE];
    Action actions[TOUCH_BUFFER_SIZE];
  };
  #pragma pack(pop)

  struct HIDPair {
    const char* key;
    uint8_t value;
  };

// ========================
// Declaración de Variables Globales
// ========================

// Variables de Estado
uint8_t previousTouchState = 0;  // Cada bit representa el estado de un pin táctil
unsigned long lastTouchTime = 0;
unsigned long lastGestureTime = 0;

// Inicialización de Bluetooth, Teclado y Almacenamiento
Preferences preferences;
const char DEVICE_NAME[] PROGMEM = "ESP32 HIDC";
const char DEVICE_MANUFACTURER[] PROGMEM = "Mystfit";

BleCompositeHID compositeHID(DEVICE_NAME, DEVICE_MANUFACTURER, 50);  // Reducido
KeyboardDevice* keyboard;

// Mapa para Convertir Acciones en Códigos HID almacenados en PROGMEM
const HIDPair HIDMap[] PROGMEM = {
    // Teclas Especiales
    {"ESPACIO", KEY_SPACE},
    {"ENTER", KEY_ENTER},
    {"ESCAPE", KEY_ESC},
    {"BACKSPACE", KEY_BACKSPACE},
    {"TAB", KEY_TAB},
    {"CAPSLOCK", KEY_CAPSLOCK},

    // Números
    {"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},

    // Letras
    {"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},

    // Símbolos
    {"-", KEY_MINUS}, {"=", KEY_EQUAL}, {"[", KEY_LEFTBRACE}, {"]", KEY_RIGHTBRACE},
    {"\\", KEY_BACKSLASH}, {";", KEY_SEMICOLON}, {"'", KEY_APOSTROPHE},
    {",", KEY_COMMA}, {".", KEY_DOT}, {"/", KEY_SLASH},
    {"`", KEY_GRAVE}, {" ", KEY_SPACE},

    // Funciones
    {"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},

    // Flechas
    {"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},

    // Modificadores
    {"LCTRL", KEY_LEFTCTRL}, {"LSHIFT", KEY_LEFTSHIFT}, {"LALT", KEY_LEFTALT}, {"LGUI", MY_KEY_LEFTGUI},
    {"RCTRL", KEY_RIGHTCTRL}, {"RSHIFT", KEY_RIGHTSHIFT}, {"RALT", KEY_RIGHTALT}, {"RGUI", MY_KEY_RIGHTGUI},

    // Single Characters
    {"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},
    {"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}, {"0", KEY_0},
    {" ", KEY_SPACE}, {"-", 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},

    // Otras teclas pueden añadirse aquí utilizando el formato {"NOMBRE", CODIGO_HID},
};

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

// ========================
// Prototipos de Funciones
// ========================
void detectTouch();
void checkGesture();
void handleSerialCommands();
bool compareSequences(const uint8_t* a, uint8_t aLen, const uint8_t* b, uint8_t bLen);
bool loadGesture(uint8_t index, Gesture* gesture);
void addGesture(const Gesture& newGesture);
void deleteGesture(uint8_t id);
uint8_t generateID();
uint8_t getHIDCode(const char* action);
bool needsShift(char c);
char getBaseChar(char c);
void logMessage(const __FlashStringHelper* message);
void logMessage(const char* message);
void listGestures();
void executeGestureActions(const Gesture& gesture);
void sendText(const char* text);
void appendToBuffer(uint8_t pin);
void clearBuffer();
void addCommandGesture(char* params);
void delCommandGesture(char* idStr);

// ========================
// Función de Configuración Inicial
// ========================
void setup() {
  Serial.begin(115200);
  while (!Serial) { /* Espera a que el puerto serie esté listo */
  }

  logMessage(F("Inicializando sistema..."));

  // Configuración del dispositivo de teclado
  KeyboardConfiguration keyboardConfig;
  keyboardConfig.setAutoReport(true);             // Activa el envío automático de informes
  keyboard = new KeyboardDevice(keyboardConfig);  // Crea el dispositivo de teclado
  compositeHID.addDevice(keyboard);               // Añade el dispositivo al composite HID

  compositeHID.begin();

  // Configura los pines táctiles como entradas
  for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
    uint8_t pin = pgm_read_byte(&TOUCH_PINS[i]);
    pinMode(pin, INPUT);
  }

  pinMode(LED_BUILTIN, OUTPUT);          // Configura el LED interno como salida
  preferences.begin("gestures", false);  // Inicia las preferencias sin modo de solo lectura

  // Registro el número de gestos almacenados
  if (!preferences.isKey("count")) {
    preferences.putUInt("count", 0);
  }
  uint8_t gestureCount = preferences.getUInt("count", 0);

  // Indicar que la configuración ha finalizado
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);  // Mantener el LED encendido por 1 segundo
  digitalWrite(LED_BUILTIN, LOW);

  logMessage(F("Sistema listo. Esperando gestos..."));
}

// ========================
// Bucle Principal
// ========================
void loop() {
  detectTouch();  // Detecta toques en los pines táctiles

  unsigned long currentTime = millis();
  // Si hubo actividad y ha pasado el tiempo de inactividad, verifica el gesto
  if (bufferSize > 0 && (currentTime - lastTouchTime > INACTIVITY_TIMEOUT_MS)) {
    checkGesture();
    clearBuffer();
    lastGestureTime = currentTime;  // Actualiza el tiempo de la última ejecución del gesto
  }

  // Maneja comandos recibidos por el puerto serie
  if (Serial.available()) {
    handleSerialCommands();
  }
}

// ========================
// Implementación de Funciones
// ========================
void detectTouch() {
  unsigned long currentTime = millis();

  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;

    // Detecta cambios en el estado del pin (de no tocado a tocado)
    if (isTouched && !(previousTouchState & (1 << i))) {
      appendToBuffer(pin);             // Agrega el pin al buffer si se toca
      lastTouchTime = currentTime;     // Actualiza el tiempo del último toque
      previousTouchState |= (1 << i);  // Marca el pin como tocado
    } else if (!isTouched && (previousTouchState & (1 << i))) {
      previousTouchState &= ~(1 << i);  // Marca el pin como no tocado
    }
  }

  // Control del LED interno basado en la inactividad reciente
  if (currentTime - lastTouchTime < 50) {
    digitalWrite(LED_BUILTIN, HIGH);
  } else if (currentTime - lastTouchTime < 100) {  // 2 * 50ms
    digitalWrite(LED_BUILTIN, LOW);
  }
}

void appendToBuffer(uint8_t pin) {
  if (bufferSize < TOUCH_BUFFER_SIZE) {
    touchBuffer[(bufferStart + bufferSize) % TOUCH_BUFFER_SIZE] = pin;
    bufferSize++;
  } else {
    // Si el buffer está lleno, reemplaza el pin más antiguo
    bufferStart = (bufferStart + 1) % TOUCH_BUFFER_SIZE;
    touchBuffer[(bufferStart + bufferSize - 1) % TOUCH_BUFFER_SIZE] = pin;
  }
}

void clearBuffer() {
  bufferStart = 0;
  bufferSize = 0;
}

bool compareSequences(const uint8_t* a, uint8_t aLen, const uint8_t* b, uint8_t bLen) {
  if (aLen < bLen) {
    return false;
  }

  // Compara las últimas bLen entradas de 'a' con 'b'
  uint8_t startA = (aLen > TOUCH_BUFFER_SIZE) ? (aLen - bLen) : 0;
  for (uint8_t i = 0; i < bLen; i++) {
    if (a[startA + i] != b[i]) {
      return false;
    }
  }
  return true;
}

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 = (uint8_t)preferences.getUInt(idKey, 0);
  if (gesture->id == 0) return false;

  // Cargar secuencia de pines
  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 = pinStr.toInt();
    if (pin != 0) {  // Pin válido
      gesture->pinSequence[gesture->length++] = pin;
    }

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

  // Cargar acciones
  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 = NORMAL_KEY;
      memset(action.text, 0, sizeof(action.text));

      // Intentar convertir a código HID
      uint8_t hidCode = getHIDCode(actionStr.c_str());
      if (hidCode != 0) {
        // Verificar si es Media Key o Modificador
        bool isMediaKey = false;
        bool isModifier = false;

        for (size_t k = 0; k < HIDMapSize; k++) {
          HIDPair pair;
          memcpy_P(&pair, &HIDMap[k], sizeof(pair));
          if (hidCode == pair.value) {
            if (hidCode >= 0xB0 && hidCode <= 0xF6) {
              isMediaKey = true;
            }
            if (hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT || hidCode == KEY_LEFTALT || hidCode == MY_KEY_LEFTGUI || hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT || hidCode == KEY_RIGHTALT || hidCode == MY_KEY_RIGHTGUI) {
              isModifier = true;
            }
            break;
          }
        }

        if (isMediaKey) {
          action.type = MEDIA_KEY;
          action.parameter = hidCode;
        } else if (isModifier) {
          action.type = MODIFIER_PRESS;
          action.parameter = hidCode;
        } else {
          action.type = NORMAL_KEY;
          action.parameter = hidCode;
        }
      } else {
        // Asume que es texto
        action.type = TEXT;
        strncpy(action.text, actionStr.c_str(), sizeof(action.text) - 1);
        action.text[sizeof(action.text) - 1] = '\0';
      }

      // Añadir acción al gesto
      gesture->actions[gesture->actionCount++] = action;
    }

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

  // Validaciones adicionales
  if (gesture->length == 0) {
    logMessage(F("Secuencia de pines inválida."));
    return false;
  }

  if (gesture->actionCount == 0) {
    logMessage(F("No se cargaron acciones para el gesto."));
    return false;
  }

  return true;
}

void checkGesture() {
  uint8_t gestureCount = preferences.getUInt("count", 0);
  if (gestureCount == 0) {
    return;
  }

  Gesture gesture;
  for (uint8_t i = 0; i < gestureCount; ++i) {
    if (!loadGesture(i, &gesture)) {
      continue;
    }

    if (compareSequences(touchBuffer + bufferStart, bufferSize, gesture.pinSequence, gesture.length)) {
      if (!compositeHID.isConnected() || keyboard == nullptr) {
        continue;
      }

      executeGestureActions(gesture);
      break;  // Sal del bucle al encontrar un gesto
    }
  }
}

void executeGestureActions(const Gesture& gesture) {
    for (uint8_t a = 0; a < gesture.actionCount; a++) {
        Action action = gesture.actions[a];
        switch (action.type) {
            case NORMAL_KEY:  // Tecla normal
                keyboard->keyPress(action.parameter);
                keyboard->keyRelease(action.parameter);
                break;
            case MEDIA_KEY:  // Media key
                keyboard->mediaKeyPress(action.parameter);
                keyboard->mediaKeyRelease(action.parameter);
                break;
            case MODIFIER_PRESS:  // Modificador (presionar)
                keyboard->modifierKeyPress(action.parameter);
                break;
            case MODIFIER_RELEASE:  // Modificador (soltar)
                keyboard->modifierKeyRelease(action.parameter);
                break;
            case TEXT:  // Enviar texto
                sendText(action.text);
                break;
            default:
                break;
        }
    }
}

void handleSerialCommands() {
  while (Serial.available()) {  // Maneja múltiples comandos a la vez
    char commandBuffer[256];
    uint16_t len = 0;

    // Lee hasta '\n' o hasta llenar el buffer
    while (Serial.available() && len < sizeof(commandBuffer) - 1) {
      char c = Serial.read();
      if (c == '\n') break;
      commandBuffer[len++] = c;
    }
    commandBuffer[len] = '\0';

    // Procesar comando
    char* command = strtok(commandBuffer, " ");
    if (command == NULL) {
      logMessage(F("Comando vacío."));
      return;
    }

    if (strcasecmp(command, "ADD") == 0) {
      // Formato esperado: ADD <secuencia_pines>|<acciones>
      char* params = strtok(NULL, "\n");
      if (params == NULL) {
        logMessage(F("Formato ADD incorrecto. Falta '|'."));
        return;
      }

      addCommandGesture(params);
    } else if (strcasecmp(command, "DEL") == 0) {
      // Formato esperado: DEL <ID>
      char* idStr = strtok(NULL, " ");
      if (idStr == NULL) {
        logMessage(F("Formato DEL incorrecto. Falta ID."));
        return;
      }
      delCommandGesture(idStr);
    } else if (strcasecmp(command, "LIST") == 0) {
      // Lista todos los gestos almacenados
      listGestures();
    } else if (strcasecmp(command, "TEST") == 0) {
      // Comando de prueba para verificar la memoria disponible
      char bufferMsg[30];
      snprintf(bufferMsg, sizeof(bufferMsg), "Memoria libre: %u bytes", ESP.getFreeHeap());
      logMessage(bufferMsg);
    } else {
      logMessage(F("Comando desconocido."));
    }
  }
}

void addCommandGesture(char* params) {
  char* delimiter = strchr(params, '|');
  if (delimiter == NULL) {
    logMessage(F("Formato ADD incorrecto. Falta '|'."));
    return;
  }

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

  // Crear nuevo gesto
  Gesture newGesture;
  memset(&newGesture, 0, sizeof(Gesture));
  newGesture.id = generateID();

  if (newGesture.id == 0) {
    logMessage(F("No hay IDs disponibles para nuevos gestos."));
    return;
  }

  // Procesar secuencia de pines
  char* pinToken = strtok(sequenceStr, " ");
  while (pinToken != NULL && newGesture.length < TOUCH_BUFFER_SIZE) {
    uint8_t pin = (uint8_t)atoi(pinToken);
    // Verificar si pin es válido
    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) {
      // Pin inválido, ignorar
      logMessage(F("Pin inválido ignorado en la secuencia."));
    }
    pinToken = strtok(NULL, " ");
  }

  if (newGesture.length == 0) {
    logMessage(F("No se añadió ningún pin válido al gesto."));
    return;
  }

  // Procesar acciones
  char* actionToken = strtok(actionsStr, " ");
  while (actionToken != NULL && newGesture.actionCount < TOUCH_BUFFER_SIZE) {
    Action action;
    memset(&action, 0, sizeof(Action));

    uint8_t hidCode = getHIDCode(actionToken);  // Actualizado para pasar la cadena completa

    if (hidCode != 0) {
      // Verificar si es Media Key o Modificador
      bool isMediaKey = false;
      bool isModifier = false;
      for (size_t k = 0; k < HIDMapSize; k++) {
        HIDPair pair;
        memcpy_P(&pair, &HIDMap[k], sizeof(pair));
        if (strcasecmp(actionToken, pair.key) == 0) {  // Comparación correcta
          if (pair.value >= 0xB0 && pair.value <= 0xF6) {
            isMediaKey = true;
          }
          if (pair.value == KEY_LEFTCTRL || pair.value == KEY_LEFTSHIFT || pair.value == KEY_LEFTALT || pair.value == MY_KEY_LEFTGUI || pair.value == KEY_RIGHTCTRL || pair.value == KEY_RIGHTSHIFT || pair.value == KEY_RIGHTALT || pair.value == MY_KEY_RIGHTGUI) {
            isModifier = true;
          }
          break;
        }
      }

      if (isMediaKey) {
        action.type = MEDIA_KEY;
        action.parameter = hidCode;
      } else if (isModifier) {
        action.type = MODIFIER_PRESS;
        action.parameter = hidCode;
      } else {
        action.type = NORMAL_KEY;
        action.parameter = hidCode;
      }
    } else {
      // Asume que es texto
      action.type = 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 se añadieron acciones válidas al gesto."));
    return;
  }

  // Añadir gesto a Preferences
  uint8_t currentCount = preferences.getUInt("count", 0);
  if (currentCount >= 255) {  // Limite ajustado a 255 para evitar problemas con generateID
    logMessage(F("Máximo número de gestos alcanzado."));
    return;
  }

  // Guardar ID
  char idKey[10];
  snprintf(idKey, sizeof(idKey), "g%u_id", currentCount);
  preferences.putUInt(idKey, newGesture.id);

  // Guardar secuencia de pines
  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 += ",";
    }
  }
  preferences.putString(seqKey, seqBuffer);

  // Guardar acciones
  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 == TEXT) {
      actsBuffer += String(newGesture.actions[a].text);
    } else {
      actsBuffer += String(newGesture.actions[a].parameter);
    }
  }
  preferences.putString(actKey, actsBuffer);

  // Actualizar cuenta de gestos
  preferences.putUInt("count", currentCount + 1);
  logMessage(F("Gesto añadido exitosamente."));
}

void delCommandGesture(char* idStr) {
  uint8_t id = (uint8_t)atoi(idStr);
  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 = (uint8_t)preferences.getUInt(idKey, 0);
    if (storedId == id) {
      // Eliminar gesto moviendo los siguientes gestos hacia atrás
      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);

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

        // Eliminar origen
        preferences.remove(srcIdKey);
        preferences.remove(srcSeqKey);
        preferences.remove(srcActKey);
      }

      // Eliminar el último gesto duplicado
      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);

      // Actualizar cuenta de gestos
      preferences.putUInt("count", gestureCount - 1);
      logMessage(F("Gesto eliminado exitosamente."));
      found = true;
      break;
    }
  }

  if (!found) {
    logMessage(F("Gesto no encontrado."));
  }
}

uint8_t generateID() {
  uint8_t gestureCount = preferences.getUInt("count", 0);
  for (uint16_t id = 1; id <= 255; id++) {
    bool exists = false;
    Gesture tempGesture;
    for (uint8_t i = 0; i < gestureCount; i++) {
      if (loadGesture(i, &tempGesture)) {
        if (tempGesture.id == id) {
          exists = true;
          break;
        }
      }
    }
    if (!exists) return id;
  }
  return 0;  // Retorna 0 si no hay IDs disponibles
}


uint8_t getHIDCode(const char* action) {
    for (size_t i = 0; i < HIDMapSize; i++) {
        HIDPair pair;
        memcpy_P(&pair, &HIDMap[i], sizeof(pair));
        if (strcasecmp(action, pair.key) == 0) {
            return pair.value;
        }
    }
    return 0;  // Si no encuentra la acción, retorna 0
}

uint8_t getHIDCode(char c) {
    char action[2];
    action[0] = c;
    action[1] = '\0';
    return getHIDCode(action);
}

bool needsShift(char c) {
  if (isupper(c)) return true;

  // Lista de símbolos que requieren Shift
  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;
}

char getBaseChar(char c) {
    if (isupper(c)) {
        return tolower(c);
    }

    switch (c) {
        case '!': return '1';
        case '@': return '2';
        case '#': return '3';
        case '$': return '4';
        case '%': return '5';
        case '^': return '6';
        case '&': return '7';
        case '*': return '8';
        case '(': return '9';
        case ')': return '0';
        case '_': return '-';
        case '+': return '=';
        case '{': return '[';
        case '}': return ']';
        case '|': return '\\';
        case ':': return ';';
        case '"': return '\'';
        case '<': return ',';
        case '>': return '.';
        case '?': return '/';
        case '~': return '`';
        default: return c;
    }
}

void logMessage(const __FlashStringHelper* message) {
  Serial.print(F("[LOG] "));
  Serial.println(message);
}

void logMessage(const char* message) {
  Serial.print("[LOG] ");
  Serial.println(message);
}

void listGestures() {
  uint8_t gestureCount = preferences.getUInt("count", 0);
  if (gestureCount == 0) {
    logMessage(F("No hay gestos almacenados."));
    return;
  }

  Serial.println(F("=== Lista de Gestos ==="));
  Gesture gesture;
  for (uint8_t i = 0; i < gestureCount; i++) {
    if (!loadGesture(i, &gesture)) {
      continue;
    }

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

    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(" | Acciones: "));
    for (uint8_t a = 0; a < gesture.actionCount; a++) {
      char actionType[30];
      switch (gesture.actions[a].type) {
        case NORMAL_KEY: strcpy(actionType, "Tecla"); break;
        case MEDIA_KEY: strcpy(actionType, "Media Key"); break;
        case MODIFIER_PRESS: strcpy(actionType, "Modificador (Presionar)"); break;
        case MODIFIER_RELEASE: strcpy(actionType, "Modificador (Soltar)"); break;
        case TEXT: strcpy(actionType, "Texto"); break;
        default: strcpy(actionType, "Desconocido"); break;
      }
      if (gesture.actions[a].type != TEXT) {
        char actionMsg[50];
        snprintf(actionMsg, sizeof(actionMsg), "%s(%u)", 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();  // Nueva línea al final de cada gesto
  }
}

void sendText(const char* text) {
    for (int c = 0; text[c] != '\0'; c++) {
        bool shift_needed = needsShift(text[c]);
        uint8_t key = getHIDCode(text[c]);

        if (shift_needed) {
            keyboard->modifierKeyPress(KEY_LEFTSHIFT);
        }

        if (key != 0) {
            keyboard->keyPress(key);
            delay(10);  // Small delay to give BLE time to process
            keyboard->keyRelease(key);
            delay(10);  // Small delay to give BLE time to process
        }

        if (shift_needed) {
            keyboard->modifierKeyRelease(KEY_LEFTSHIFT);
            delay(10);  // Small delay to give BLE time to process
        }

        // Check free heap periodically
        if (c % 10 == 0) {  // Check every 10 characters
            logMessage(F("Free heap: "));
            logMessage(String(ESP.getFreeHeap()).c_str());
        }
    }
}

// Funciones de Gestión de Gestos desde Serial
void addGesture(const Gesture& newGesture) {
  // Implementar si es necesario
}

void deleteGesture(uint8_t id) {
  // Implementar si es necesario
}

// ========================
// Optimización y Mejoras Adicionales
// ========================

// Para mayor eficiencia, se recomienda utilizar funciones optimizadas para acceder a PROGMEM
// y evitar múltiples llamadas a `pgm_read_byte` dentro de loops críticos.
// Además, optimizamos la búsqueda en HIDMap utilizando un enfoque más eficiente,
// como mantener HIDMap ordenado y aplicar búsqueda binaria, si es necesario.
```
	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);
			}
		}
	}
	```