tu tarea sera corregir y mejorar mi codigo base en base a la informacion y ejemplos que te entregare. busco que mi codigo sea de nivel profesional, buscando optimizarlo hasta su nivel de eficiencia maximo. he estado intentadndo refinarlo hasta su mejor punto, limando todo error en este. he estado intentando agregar caracteristicas nuevas al codigo, mejorandolo todo lo que puedo. quiero que me ayudes con todo lo que puedas, buscando que hagas un mejor trabajo que yo en lo que he hecho en con todos estos enfoques que he tomado.
adicionalmente, quisiera que implementaras todas las teclas de la libreria que utilizo en mi codigo.
mi código a corregir:
```
// ========================
// Proyecto ESP32 HID
// ========================
// ========================
// Inclusión de Librerías y Definiciones
// ========================
  #include <Arduino.h>
  #include <BleCompositeHID.h>
  #include <KeyboardDevice.h>
  #include <Preferences.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 = 0xE3;
  const uint8_t MY_KEY_RIGHTGUI        PROGMEM = 0xE7;
  // ... [Otros códigos HID] ...

  // 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};
  const uint8_t NUM_TOUCH_PINS = sizeof(TOUCH_PINS) / sizeof(TOUCH_PINS[0]);

  // Parámetros de Gestos
  const uint16_t INACTIVITY_TIMEOUT_MS = 500;  // Tiempo en ms

  // Buffers y Contadores
  constexpr uint8_t TOUCH_BUFFER_SIZE = 100;
  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 en PROGMEM
  const HIDPair HIDMap[] PROGMEM = {
    // Teclas Especiales
    {"ESPACIO", KEY_SPACE},
    {"ENTER", KEY_ENTER},
    {"ESCAPE", KEY_ESC},
    // ... [Otros mapeos] ...
    // Números
    {"0", 39}, {"1", 30}, {"2", 31}, {"3", 32}, {"4", 33},
    {"5", 34}, {"6", 35}, {"7", 36}, {"8", 37}, {"9", 38},
    // Letras
    {"A", 4}, {"B", 5}, {"C", 6}, {"D", 7}, {"E", 8},
    // ... [Otros mapeos] ...
    // Símbolos
    {"/", 55},  // Se maneja '?' como shift + '/'
    {" ", 44}
  };
  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);                // Llamada corregida sin verificación

    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 número
        uint8_t hidCode = actionStr.toInt();
        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) {
                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 (strcmp(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 (strcmp(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 (strcmp(command, "LIST") == 0) {
        // Lista todos los gestos almacenados
        listGestures();
      }
      else if (strcmp(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();

    // 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) {
              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 >= 100) {
      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);
        }

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

  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]);

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

      char baseChar = getBaseChar(text[c]);
      uint8_t key = getHIDCode(&baseChar);  // Actualizado para pasar un puntero

      if (key != 0) {
        keyboard->keyPress(key);
        keyboard->keyRelease(key);
      }

      if (shift_needed) {
        keyboard->modifierKeyRelease(KEY_LEFTSHIFT);
      }
    }
  }
```

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);
        }
    }
}
```