Refactor my code to a professional level by modularizing it exceptionally, eliminating redundancies and dead code, optimizing it for maximum efficiency, thoroughly documenting it with clear and concise comments, adding comprehensive unit tests with high coverage, applying consistent and clean styling following industry standards, adhering to best practices and design patterns, ensuring scalability and maintainability, following language-specific conventions, implementing robust error handling, enhancing security where applicable, optimizing performance, and providing a detailed summary of all changes made. Additionally, ensure the code is compatible with the latest standards and frameworks, includes meaningful variable and function names, removes any hard-coded values by using configuration files or environment variables, and verifies that all dependencies are up to date. To complete this task, reason step by step to deliver the best result. my code is in arduino ide, and i use a esp32 so i only can use one code.
ERRORS:
```
The code appears to be well-structured and follows good practices. However, here are some suggestions for improvement:

1. **Comments**: While there are comments throughout the code, they could be more descriptive and provide better context. Consider adding more comments to explain the purpose of each function, class, and section of code.

2. **Function Length**: Some functions, such as `setup()` and `loop()`, are quite long and perform multiple tasks. Consider breaking them down into smaller functions, each responsible for a specific task. This will make the code easier to read and maintain.

3. **Magic Numbers**: The code contains several magic numbers (e.g., `115200`, `50`, `TOUCH_BUFFER_SIZE`). Consider defining these numbers as constants at the top of the file to make their meaning clearer.

4. **Error Handling**: The code does not seem to have robust error handling. For example, if the `Preferences` instance fails to begin, the program will print an error message but continue running. Consider adding more comprehensive error handling to ensure the program behaves correctly in unexpected situations.

5. **Code Duplication**: There is some duplicated code between `executeMatchingGesture()` and `serialHandler.handleCommands()`. Consider extracting the common logic into a separate function to avoid duplication.

6. **Naming Conventions**: The code uses both camelCase and underscore notation for variable names. Consider sticking to a single convention throughout the codebase.

7. **Class Structure**: The classes (`GestureManager`, `KeyboardManager`, etc.) seem to be doing too much. Consider breaking them down into smaller classes, each responsible for a specific aspect of the program's functionality.

8. **Dependency Injection**: The classes have tight dependencies on other classes and objects. Consider using dependency injection to make the code more modular and testable.

9. **Testing**: There do not appear to be any tests for the code. Consider writing unit tests and integration tests to ensure the code works correctly and catch any regressions.

10. **Documentation**: The code could benefit from more documentation, especially for the classes and functions. Consider adding Doxygen-style comments to provide a clear understanding of the code's functionality.

By addressing these areas, you can improve the maintainability, readability, and overall quality of the code.
```
THIS IS MY CODE:
```
// ========================
// ESP32 HID Controller - Refactorizado
// ========================

/**
 * @file HIDController.ino
 * @brief Código refactorizado de nivel profesional para el controlador HID ESP32 usando Arduino IDE.
 * 
 * Este proyecto permite acciones HID basadas en gestos a través de BLE utilizando un microcontrolador ESP32.
 * El código está organizado en clases para promover la modularidad, mantenibilidad y escalabilidad.
 * Se proporcionan comentarios comprensivos para mayor claridad y entendimiento.
 * 
 * Características:
 * - Clases modulares para Gestión de Gestos, Acciones de Teclado, Comandos Seriales y Manejo de Toques.
 * - Parsing mejorado de acciones para soportar acciones con múltiples palabras usando delimitadores de punto y coma.
 * - Mapeo HID optimizado con búsquedas insensibles a mayúsculas y minúsculas.
 * - Gestión centralizada de constantes de configuración para eliminar valores codificados.
 * - Manejo robusto de errores y logging informativo.
 * - Estilo de código consistente y limpio siguiendo estándares de la industria.
 * - Espacio reservado para pruebas unitarias (para implementar según sea necesario).
 */

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

// ========================
// Constantes de Configuración
// ========================

// Información del dispositivo
const char DEVICE_NAME[] = "ESP32 HIDC";
const char DEVICE_MANUFACTURER[] = "Mystfit";

// Configuración de 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 de espera en milisegundos
constexpr uint8_t TOUCH_BUFFER_SIZE = 100;
constexpr uint8_t MAX_GESTURES = 255;

// Configuración de LED
constexpr uint8_t LED_PIN = LED_BUILTIN;

// Configuración de Logs
#define LOG_PREFIX "[LOG] "

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

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

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

struct Gesture {
    uint8_t id;
    uint8_t length;
    uint8_t actionCount;
    uint8_t pinSequence[TOUCH_BUFFER_SIZE];
    Action actions[TOUCH_BUFFER_SIZE];

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

// ========================
// Mapeo de Claves HID
// ========================
struct HIDPair {
    const char* key;
    uint8_t value;
};

// Mapa HID almacenado en PROGMEM
const HIDPair HIDMap[] PROGMEM = {
    // Claves especiales
    {"SPACE", 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_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},

    // Teclas de función
    {"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},

    // Teclas de flecha
    {"UP", KEY_UP}, {"DOWN", KEY_DOWN}, {"LEFT", KEY_LEFT}, {"RIGHT", KEY_RIGHT},

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

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

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

// ========================
// Funciones Utilitarias
// ========================

/**
 * @brief Obtiene el código HID para una cadena de acción dada.
 *        Realiza una búsqueda insensible a mayúsculas y minúsculas.
 * @param action Cadena de acción.
 * @return Código HID si se encuentra, 0 de lo contrario.
 */
uint8_t getHIDCode(const char* action) {
    if (action == nullptr) return 0;

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

    // Crear un buffer temporal para comparación insensible a mayúsculas
    char tempAction[32];
    strncpy(tempAction, action, sizeof(tempAction) - 1);
    tempAction[sizeof(tempAction) - 1] = '\0';

    // Convertir a mayúsculas para comparación
    for (size_t i = 0; i < strlen(tempAction); i++) {
        tempAction[i] = toupper(tempAction[i]);
    }

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

/**
 * @brief Determina si un carácter requiere la tecla Shift.
 *        Incluye una gama más amplia de símbolos.
 * @param c Carácter a verificar.
 * @return true si se necesita Shift, false de lo contrario.
 */
bool needsShift(char c) {
    if (isupper(c)) return true;

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

// ========================
// Clases
// ========================

/**
 * @class GestureManager
 * @brief Gestiona los gestos incluyendo carga, adición, eliminación y listado.
 */
class GestureManager {
public:
    /**
     * @brief Constructor de GestureManager.
     * @param prefs Referencia a la instancia de Preferences.
     */
    GestureManager(Preferences& prefs) : preferences(prefs) {}

    /**
     * @brief Carga un gesto del almacenamiento de preferencias.
     * @param index Índice del gesto a cargar.
     * @param gesture Referencia a un objeto Gesture a llenar.
     * @return true si el gesto se cargó exitosamente, false de lo contrario.
     */
    bool loadGesture(uint8_t index, Gesture& gesture) {
        char idKey[10];
        snprintf(idKey, sizeof(idKey), "g%u_id", index);
        if (!preferences.isKey(idKey)) return false;

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

        // 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 = static_cast<uint8_t>(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 = ActionType::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) {
                    // Determinar tipo de acción basado en rangos de códigos HID
                    if (hidCode >= 0xB0 && hidCode <= 0xF6) {
                        action.type = ActionType::MEDIA_KEY;
                        action.parameter = hidCode;
                    }
                    else if (
                        hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT ||
                        hidCode == KEY_LEFTALT || hidCode == KEY_LEFTMETA ||
                        hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT ||
                        hidCode == KEY_RIGHTALT || hidCode == KEY_RIGHTMETA) {
                        action.type = ActionType::MODIFIER_PRESS;
                        action.parameter = hidCode;
                    }
                    else {
                        action.type = ActionType::NORMAL_KEY;
                        action.parameter = hidCode;
                    }
                }
                else {
                    // Asumir que es texto
                    action.type = ActionType::TEXT;
                    strncpy(action.text, actionStr.c_str(), sizeof(action.text) - 1);
                    action.text[sizeof(action.text) - 1] = '\0';
                }

                // Agregar acción al gesto
                gesture.actions[gesture.actionCount++] = action;
            }

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

        // Validaciones adicionales
        return (gesture.length > 0) && (gesture.actionCount > 0);
    }

    /**
     * @brief Compara dos secuencias para determinar si coinciden.
     * @param a Primera secuencia.
     * @param aLen Longitud de la primera secuencia.
     * @param b Segunda secuencia.
     * @param bLen Longitud de la segunda secuencia.
     * @return true si las secuencias coinciden, false de lo contrario.
     */
    bool compareSequences(const uint8_t* a, uint8_t aLen, const uint8_t* b, uint8_t bLen) {
        if (aLen < bLen) {
            return false;
        }

        // Comparar los últimos bLen elementos de 'a' con 'b'
        uint8_t startA = (aLen > bLen) ? (aLen - bLen) : 0;
        for (uint8_t i = 0; i < bLen; i++) {
            if (a[startA + i] != b[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * @brief Genera un ID único para un nuevo gesto.
     * @return ID único si está disponible, 0 de lo contrario.
     */
    uint8_t generateID() {
        uint8_t gestureCount = preferences.getUInt("count", 0);
        for (uint16_t id = 1; id <= MAX_GESTURES; id++) {
            bool exists = false;
            Gesture tempGesture;
            for (uint8_t i = 0; i < gestureCount; i++) {
                if (loadGesture(i, tempGesture)) {
                    if (tempGesture.id == id) {
                        exists = true;
                        break;
                    }
                }
            }
            if (!exists) return id;
        }
        return 0; // Retornar 0 si no hay IDs disponibles
    }

    /**
     * @brief Agrega un nuevo gesto al almacenamiento de preferencias.
     * @param newGesture Gesto a agregar.
     * @return true si se agregó exitosamente, false de lo contrario.
     */
    bool addGesture(const Gesture& newGesture) {
        if (newGesture.id == 0) return false;

        uint8_t currentCount = preferences.getUInt("count", 0);
        if (currentCount >= MAX_GESTURES) { // Limite a MAX_GESTURES gestos
            return false;
        }

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

        // Actualizar conteo de gestos
        preferences.putUInt("count", currentCount + 1);
        return true;
    }

    /**
     * @brief Elimina un gesto por su ID.
     * @param id ID del gesto a eliminar.
     * @return true si se eliminó exitosamente, false de lo contrario.
     */
    bool deleteGesture(uint8_t id) {
        uint8_t gestureCount = preferences.getUInt("count", 0);
        bool found = false;

        for (uint8_t i = 0; i < gestureCount; i++) {
            char idKey[10];
            snprintf(idKey, sizeof(idKey), "g%u_id", i);
            uint8_t storedId = static_cast<uint8_t>(preferences.getUInt(idKey, 0));
            if (storedId == id) {
                // Desplazar todos los gestos posteriores hacia arriba
                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);

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

                    // Remover fuente
                    preferences.remove(srcIdKey);
                    preferences.remove(srcSeqKey);
                    preferences.remove(srcActKey);
                }

                // Remover el último gesto
                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 conteo de gestos
                preferences.putUInt("count", gestureCount - 1);
                found = true;
                break;
            }
        }

        return found;
    }

    /**
     * @brief Retorna el número actual de gestos almacenados.
     * @return Número de gestos.
     */
    uint8_t getGestureCount() const {
        return static_cast<uint8_t>(preferences.getUInt("count", 0));
    }

    /**
     * @brief Lista todos los gestos almacenados a través del Serial.
     */
    void listGestures() const {
        uint8_t gestureCount = getGestureCount();
        if (gestureCount == 0) {
            Serial.println(F(LOG_PREFIX "No gestures stored."));
            return;
        }

        Serial.println(F("=== List of Gestures ==="));
        Gesture gesture;
        for (uint8_t i = 0; i < gestureCount; i++) {
            // Necesitamos usar const_cast para llamar a loadGesture en un método const
            if (!const_cast<GestureManager*>(this)->loadGesture(i, gesture)) {
                Serial.println(F(LOG_PREFIX "Failed to load gesture."));
                continue;
            }

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

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

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

private:
    Preferences& preferences;
};

/**
 * @class KeyboardManager
 * @brief Gestiona las acciones de teclado basadas en gestos.
 */
class KeyboardManager {
public:
    /**
     * @brief Constructor de KeyboardManager.
     * @param kbDevice Puntero a la instancia de KeyboardDevice.
     */
    KeyboardManager(KeyboardDevice* kbDevice) : keyboard(kbDevice) {}

    /**
     * @brief Ejecuta las acciones definidas en un gesto.
     * @param gesture Gesto que contiene las acciones a ejecutar.
     */
    void executeGestureActions(const Gesture& gesture) {
        Serial.print(F(LOG_PREFIX "Executing actions for gesture ID: "));
        Serial.println(gesture.id);
        for (uint8_t a = 0; a < gesture.actionCount; a++) {
            const Action& action = gesture.actions[a];
            switch (action.type) {
                case ActionType::NORMAL_KEY:
                    Serial.print(F(LOG_PREFIX "Pressing normal key code: 0x"));
                    Serial.println(action.parameter, HEX);
                    keyboard->keyPress(action.parameter);
                    keyboard->keyRelease(action.parameter);
                    break;
                case ActionType::MEDIA_KEY:
                    Serial.print(F(LOG_PREFIX "Pressing media key code: 0x"));
                    Serial.println(action.parameter, HEX);
                    keyboard->mediaKeyPress(action.parameter);
                    keyboard->mediaKeyRelease(action.parameter);
                    break;
                case ActionType::MODIFIER_PRESS:
                    Serial.print(F(LOG_PREFIX "Pressing modifier key code: 0x"));
                    Serial.println(action.parameter, HEX);
                    keyboard->modifierKeyPress(action.parameter);
                    break;
                case ActionType::MODIFIER_RELEASE:
                    Serial.print(F(LOG_PREFIX "Releasing modifier key code: 0x"));
                    Serial.println(action.parameter, HEX);
                    keyboard->modifierKeyRelease(action.parameter);
                    break;
                case ActionType::TEXT:
                    Serial.print(F(LOG_PREFIX "Sending text: "));
                    Serial.println(action.text);
                    sendText(action.text);
                    break;
                default:
                    Serial.println(F(LOG_PREFIX "Unknown action type."));
                    break;
            }
        }
    }

    /**
     * @brief Envía una cadena de texto como entrada de teclado.
     * @param text Texto a enviar.
     */
    void sendText(const char* text) {
        for (int c = 0; text[c] != '\0'; c++) {
            char currentChar = text[c];
            bool shift_needed = needsShift(currentChar);
            uint8_t key = getHIDCode(currentChar);

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

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

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

            // Verificación periódica de memoria
            if (c % 10 == 0 && c != 0) {
                Serial.print(F("Free memory: "));
                Serial.println(ESP.getFreeHeap());
            }
        }
    }

private:
    KeyboardDevice* keyboard;

    /**
     * @brief Obtiene el código HID para un carácter dado.
     * @param c Carácter a convertir.
     * @return Código HID.
     */
    uint8_t getHIDCode(char c) {
        return ::getHIDCode(&c); // Utiliza la función global corregida
    }
};

/**
 * @class SerialCommandHandler
 * @brief Maneja comandos seriales para gestionar gestos.
 */
class SerialCommandHandler {
public:
    /**
     * @brief Constructor de SerialCommandHandler.
     * @param gm Puntero a la instancia de GestureManager.
     */
    SerialCommandHandler(GestureManager* gm) : gestureManager(gm) {}

    /**
     * @brief Procesa comandos seriales entrantes.
     */
    void handleCommands() {
        while (Serial.available()) {  // Manejar múltiples comandos a la vez
            char commandBuffer[256];
            uint16_t len = 0;

            // Leer hasta '\n' o hasta que el buffer esté lleno
            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("Empty command."));
                return;
            }

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

                addCommandGesture(params);
            }
            else if (strcasecmp(command, "DEL") == 0) {
                // Formato esperado: DEL <ID>
                char* idStr = strtok(NULL, " ");
                if (idStr == NULL) {
                    logMessage(F("Invalid DEL format. Usage: DEL <ID>"));
                    return;
                }
                delCommandGesture(idStr);
            }
            else if (strcasecmp(command, "LIST") == 0) {
                // Listar todos los gestos almacenados
                gestureManager->listGestures();
            }
            else if (strcasecmp(command, "TEST") == 0) {
                // Comando de prueba para verificar memoria disponible
                char bufferMsg[50];
                snprintf(bufferMsg, sizeof(bufferMsg), "Free memory: %u bytes", ESP.getFreeHeap());
                logMessage(bufferMsg);
            }
            else {
                logMessage(F("Unknown command."));
            }
        }
    }

private:
    GestureManager* gestureManager;

    /**
     * @brief Registra mensajes con un formato consistente.
     * @param message Mensaje a registrar.
     */
    void logMessage(const __FlashStringHelper* message) {
        Serial.print(F(LOG_PREFIX));
        Serial.println(message);
    }

    void logMessage(const char* message) {
        Serial.print(LOG_PREFIX);
        Serial.println(message);
    }

    /**
     * @brief Agrega un gesto basado en los parámetros del comando serial.
     * @param params Parámetros que contienen la secuencia de pines y acciones.
     */
    void addCommandGesture(char* params) {
        char* delimiter = strchr(params, '|');
        if (delimiter == NULL) {
            logMessage(F("Invalid ADD format. Missing '|'. Usage: ADD <pin_sequence>|<actions>"));
            return;
        }

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

        // Crear nuevo gesto
        Gesture newGesture;
        newGesture.id = gestureManager->generateID();

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

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

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

        // Procesar acciones usando ';' como delimitador para soportar acciones con múltiples palabras
        char* actionToken = strtok(actionsStr, ";");
        while (actionToken != NULL && newGesture.actionCount < TOUCH_BUFFER_SIZE) {
            Action action;
            action.type = ActionType::NORMAL_KEY;
            memset(action.text, 0, sizeof(action.text));

            // Convertir token de acción a código HID
            uint8_t hidCode = getHIDCode(actionToken);

            if (hidCode != 0) {
                // Determinar tipo de acción basado en rangos de códigos HID
                if (hidCode >= 0xB0 && hidCode <= 0xF6) {
                    action.type = ActionType::MEDIA_KEY;
                    action.parameter = hidCode;
                }
                else if (
                    hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT ||
                    hidCode == KEY_LEFTALT || hidCode == KEY_LEFTMETA ||
                    hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT ||
                    hidCode == KEY_RIGHTALT || hidCode == KEY_RIGHTMETA) {
                    action.type = ActionType::MODIFIER_PRESS;
                    action.parameter = hidCode;
                }
                else {
                    action.type = ActionType::NORMAL_KEY;
                    action.parameter = hidCode;
                }
            }
            else {
                // Asumir que es texto
                action.type = ActionType::TEXT;
                strncpy(action.text, actionToken, sizeof(action.text) - 1);
                action.text[sizeof(action.text) - 1] = '\0';
            }

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

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

        // Agregar gesto a las preferencias
        if (gestureManager->addGesture(newGesture)) {
            logMessage(F("Gesture added successfully."));
        }
        else {
            logMessage(F("Failed to add gesture."));
        }
    }

    /**
     * @brief Elimina un gesto basado en su ID.
     * @param idStr Cadena que representa el ID.
     */
    void delCommandGesture(char* idStr) {
        uint8_t id = static_cast<uint8_t>(atoi(idStr));
        if (gestureManager->deleteGesture(id)) {
            logMessage(F("Gesture deleted successfully."));
        }
        else {
            logMessage(F("Gesture not found."));
        }
    }
};

/**
 * @class TouchHandler
 * @brief Maneja las entradas táctiles y la retroalimentación LED.
 */
class TouchHandler {
public:
    /**
     * @brief Constructor de TouchHandler.
     */
    TouchHandler() : previousTouchState(0), lastActivityTime(0) {}

    /**
     * @brief Inicializa los pines táctiles y el LED.
     */
    void initialize() {
        for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
            uint8_t pin = pgm_read_byte(&TOUCH_PINS[i]);
            pinMode(pin, INPUT);
        }
        pinMode(LED_PIN, OUTPUT); // Configurar el LED incorporado como salida
        digitalWrite(LED_PIN, LOW); // Apagar el LED inicialmente
    }

    /**
     * @brief Detecta eventos táctiles.
     * @param pinDetected Número de pin que fue tocado.
     * @param touched Bandera booleana indicando si se detectó un toque.
     */
    void detectTouch(uint8_t& pinDetected, bool& touched) {
        touched = false;
        pinDetected = 0;

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

            // Detectar cambio de no tocado a tocado
            if (isTouched && !(previousTouchState & (1 << i))) {
                pinDetected = pin;
                touched = true;
                previousTouchState |= (1 << i); // Marcar como tocado
                break; // Manejar un toque a la vez
            }
            else if (!isTouched && (previousTouchState & (1 << i))) {
                previousTouchState &= ~(1 << i); // Marcar como no tocado
            }
        }

        // Controlar el LED basado en la actividad reciente
        if (touched) {
            setLEDState(true);
            lastActivityTime = millis();
        }
        else {
            // Apagar LED si la inactividad excede un umbral corto
            if (millis() - lastActivityTime > 50) {
                setLEDState(false);
            }
        }
    }

private:
    uint8_t previousTouchState;
    unsigned long lastActivityTime;

    /**
     * @brief Establece el estado del LED incorporado.
     * @param state True para encender, false para apagar.
     */
    void setLEDState(bool state) {
        digitalWrite(LED_PIN, state ? HIGH : LOW);
    }
};

// ========================
// Objetos Globales
// ========================
Preferences preferencesInstance; // Almacenamiento de preferencias
GestureManager gestureManager(preferencesInstance);
KeyboardDevice* keyboard = nullptr; // Puntero al dispositivo de teclado
KeyboardManager keyboardManager(nullptr); // Inicializado más tarde
SerialCommandHandler serialHandler(&gestureManager);
TouchHandler touchHandler;

// Buffer táctil y variables de tiempo
uint8_t touchBufferGlobal[TOUCH_BUFFER_SIZE];
uint16_t bufferStart = 0;
uint16_t bufferSize = 0;
unsigned long lastTouchTime = 0;
unsigned long lastGestureTime = 0;

// Dispositivos BLE y de Teclado
BleCompositeHID compositeHID(DEVICE_NAME, DEVICE_MANUFACTURER, 50); // Debe declararse DESPUÉS de DEVICE_NAME y DEVICE_MANUFACTURER

// ========================
// Funciones Auxiliares
// ========================

/**
 * @brief Agrega un pin al buffer táctil.
 * @param pin Número de pin a agregar.
 */
void appendToBuffer(uint8_t pin) {
    if (bufferSize < TOUCH_BUFFER_SIZE) {
        touchBufferGlobal[(bufferStart + bufferSize) % TOUCH_BUFFER_SIZE] = pin;
        bufferSize++;
    }
    else {
        // Si el buffer está lleno, sobrescribir la entrada más antigua
        bufferStart = (bufferStart + 1) % TOUCH_BUFFER_SIZE;
        touchBufferGlobal[(bufferStart + bufferSize - 1) % TOUCH_BUFFER_SIZE] = pin;
    }
}

/**
 * @brief Limpia el buffer táctil.
 */
void clearBuffer() {
    bufferStart = 0;
    bufferSize = 0;
    Serial.println(F(LOG_PREFIX "Buffer cleared."));
}

/**
 * @brief Ejecuta un gesto que coincide basado en el buffer táctil actual.
 */
void executeMatchingGesture() {
    Serial.println(F(LOG_PREFIX "Verifying gesture..."));
    uint8_t gestureCount = gestureManager.getGestureCount();
    if (gestureCount == 0) {
        Serial.println(F(LOG_PREFIX "No gestures stored."));
    }
    else {
        Gesture gesture;
        for (uint8_t i = 0; i < gestureCount; ++i) {
            if (!gestureManager.loadGesture(i, gesture)) {
                Serial.println(F(LOG_PREFIX "Failed to load gesture."));
                continue;
            }

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

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

                keyboardManager.executeGestureActions(gesture);
                Serial.println(F(LOG_PREFIX "Gesture actions executed."));
                break; // Salir del ciclo después de ejecutar el primer gesto coincidente
            }
        }
    }
    clearBuffer();
    lastGestureTime = millis();
}

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

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

    // Inicializar el Dispositivo de Teclado
    KeyboardConfiguration keyboardConfig;
    keyboardConfig.setAutoReport(true);
    keyboard = new KeyboardDevice(keyboardConfig);
    compositeHID.addDevice(keyboard);

    compositeHID.begin();

    // Inicializar el Manejador de Toques
    touchHandler.initialize();

    // Inicializar el Manejador de Teclado con el dispositivo de teclado
    keyboardManager = KeyboardManager(keyboard);

    // Inicializar el Manejador de Comandos Seriales
    // Ya está inicializado durante la creación del objeto global

    // Inicializar el conteo de gestos si no está presente
    if (!preferencesInstance.begin("gestures", false)) {
        Serial.println(F(LOG_PREFIX "Failed to initialize Preferences."));
    }

    if (!preferencesInstance.isKey("count")) {
        preferencesInstance.putUInt("count", 0);
    }

    // Indicar la finalización de la configuración a través del LED
    digitalWrite(LED_PIN, HIGH);
    delay(1000); // Mantener el LED encendido durante 1 segundo
    digitalWrite(LED_PIN, LOW);

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

// ========================
// Función de Loop Principal
// ========================
void loop() {
    uint8_t touchedPin = 0;
    bool touched = false;
    touchHandler.detectTouch(touchedPin, touched);

    unsigned long currentTime = millis();

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

    // Si la actividad ha estado inactiva durante el período de tiempo de espera, verificar gestos
    if (bufferSize > 0 && (currentTime - lastTouchTime > INACTIVITY_TIMEOUT_MS)) {
        executeMatchingGesture();
    }

    // Manejar comandos seriales
    if (Serial.available()) {
        serialHandler.handleCommands();
    }

    // Opcional: Implementar tareas periódicas o funcionalidades adicionales aquí
}
```
	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);
			}
		}
	}
	```