C:\Users\roman\OneDrive\Documentos\Arduino\3_pr\3_pr.ino:1476:6: error: #error "Failed to allocate KeyboardDevice."
 1476 |     #error "Failed to allocate KeyboardDevice."
      |      ^~~~~
exit status 1

Compilation error: #error "Failed to allocate KeyboardDevice."

codigo:
```
// ========================
// ESP32 HID Controller - Professional Refactored Version
// ========================

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // Function Keys
    {"F1", KEY_F1}, {"F2", KEY_F2}, {"F3", KEY_F3}, {"F4", KEY_F4},
    {"F5", KEY_F5}, {"F6", KEY_F6}, {"F7", KEY_F7}, {"F8", KEY_F8},
    {"F9", KEY_F9}, {"F10", KEY_F10}, {"F11", KEY_F11}, {"F12", KEY_F12},
    {"F13", KEY_F13}, {"F14", KEY_F14}, {"F15", KEY_F15}, {"F16", KEY_F16},
    {"F17", KEY_F17}, {"F18", KEY_F18}, {"F19", KEY_F19}, {"F20", KEY_F20},
    {"F21", KEY_F21}, {"F22", KEY_F22}, {"F23", KEY_F23}, {"F24", KEY_F24},

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

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

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

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

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

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

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

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

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

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

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

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

// ========================
// Classes
// ========================

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

    /**
     * @brief Loads a gesture from preferences storage.
     * @param index Index of the gesture to load.
     * @param gesture Reference to a Gesture object to populate.
     * @return True if gesture loaded successfully, false otherwise.
     */
    bool loadGesture(uint8_t index, Gesture& gesture) {
        char idKey[10];
        snprintf(idKey, sizeof(idKey), "g%u_id", index);
        if (!preferences.isKey(idKey)) return false;

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

        // Load pin sequence
        char seqKey[10];
        snprintf(seqKey, sizeof(seqKey), "g%u_seq", index);
        String sequenceStr = preferences.getString(seqKey, "");
        gesture.length = 0;
        int start = 0;
        while (start < sequenceStr.length() && gesture.length < TOUCH_BUFFER_SIZE) {
            int comma = sequenceStr.indexOf(',', start);
            String pinStr = (comma == -1) ? sequenceStr.substring(start) : sequenceStr.substring(start, comma);
            pinStr.trim();

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

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

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

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

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

                if (hidCode != 0) {
                    // Determine action type based on HID code ranges
                    if (hidCode >= 0xB0 && hidCode <= 0xF6) {
                        action.type = ActionType::MEDIA_KEY;
                        action.parameter = hidCode;
                    }
                    else if (
                        hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT ||
                        hidCode == KEY_LEFTALT || hidCode == KEY_LEFTMETA ||
                        hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT ||
                        hidCode == KEY_RIGHTALT || hidCode == KEY_RIGHTMETA) {
                        action.type = ActionType::MODIFIER_PRESS;
                        action.parameter = hidCode;
                    }
                    else {
                        action.type = ActionType::NORMAL_KEY;
                        action.parameter = hidCode;
                    }
                }
                else {
                    // Assume it's text
                    action.type = ActionType::TEXT;
                    strncpy(action.text, actionStr.c_str(), sizeof(action.text) - 1);
                    action.text[sizeof(action.text) - 1] = '\0';
                }

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

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

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

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

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

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

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

        return true;
    }

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

        // Remove gesture entries
        char idKey[10], seqKey[10], actKey[10];
        snprintf(idKey, sizeof(idKey), "g%u_id", index);
        snprintf(seqKey, sizeof(seqKey), "g%u_seq", index);
        snprintf(actKey, sizeof(actKey), "g%u_acts", index);
        preferences.remove(idKey);
        preferences.remove(seqKey);
        preferences.remove(actKey);
        return true;
    }

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

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

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

/**
 * @class GestureComparator
 * @brief Compares gesture sequences efficiently using Knuth-Morris-Pratt (KMP) algorithm.
 */
class GestureComparator {
public:
    /**
     * @brief Compares two sequences using KMP algorithm.
     * @param haystack Pointer to the first sequence.
     * @param haystackLen Length of the first sequence.
     * @param needle Pointer to the second sequence.
     * @param needleLen Length of the second sequence.
     * @return True if the needle sequence is found at the end of haystack, false otherwise.
     */
    bool compareSequencesKMP(const uint8_t* haystack, uint8_t haystackLen, const uint8_t* needle, uint8_t needleLen) {
        if (needleLen == 0 || haystackLen < needleLen) return false;

        // Preprocess the needle to create lps array
        uint8_t lps[needleLen];
        computeLPSArray(needle, needleLen, lps);

        uint8_t i = haystackLen - needleLen; // Start comparing from position where needle could fit
        uint8_t j = 0; // Index for needle

        for (; i < haystackLen; i++) {
            while (j > 0 && haystack[i] != needle[j]) {
                j = lps[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needleLen) {
                return true; // Match found
            }
        }
        return false; // No match
    }

private:
    /**
     * @brief Computes the Longest Prefix Suffix (LPS) array for KMP algorithm.
     * @param needle Pointer to the needle sequence.
     * @param needleLen Length of the needle sequence.
     * @param lps Array to store the computed LPS values.
     */
    void computeLPSArray(const uint8_t* needle, uint8_t needleLen, uint8_t* lps) {
        uint8_t length = 0;
        lps[0] = 0; // LPS[0] is always 0

        for (uint8_t i = 1; i < needleLen; i++) {
            while (length > 0 && needle[i] != needle[length]) {
                length = lps[length - 1];
            }
            if (needle[i] == needle[length]) {
                length++;
            }
            lps[i] = length;
        }
    }
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @brief Sets the GestureComparator instance.
     * @param comparator Pointer to the GestureComparator instance.
     */
    void setComparator(GestureComparator* comparator) {
        gestureComparator = comparator;
    }

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

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

    /**
     * @brief Processes incoming serial commands.
     */
    void handleCommands() {
        while (Serial.available()) {  // Handle multiple commands at once
            char commandBuffer[256];
            uint16_t len = 0;

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

            // Trim carriage return if present
            if (len > 0 && commandBuffer[len - 1] == '\r') {
                commandBuffer[len - 1] = '\0';
                len--;
            }

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

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

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

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

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

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

    /**
     * @brief Adds a gesture based on serial command parameters.
     * @param params Parameters containing pin sequence and actions.
     */
    void addCommandGesture(char* params) {
        char* delimiter = strchr(params, '|');
        if (delimiter == NULL) {
            logMessage(F("Invalid ADD format. Missing '|'. Usage: ADD <pin_sequence>|<actions>"));
            return;
        }

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

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

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

        // Process pin sequence
        char* pinToken = strtok(sequenceStr, " ");
        while (pinToken != NULL && newGesture.length < TOUCH_BUFFER_SIZE) {
            uint8_t pin = static_cast<uint8_t>(atoi(pinToken));
            // Validate pin
            if (isValidPin(pin)) {
                newGesture.pinSequence[newGesture.length++] = pin;
            }
            else {
                logMessage(F("Invalid pin ignored in sequence."));
            }
            pinToken = strtok(NULL, " ");
        }

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

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

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

            if (hidCode != 0) {
                // Determine action type based on HID code ranges
                if (hidCode >= 0xB0 && hidCode <= 0xF6) {
                    action.type = ActionType::MEDIA_KEY;
                    action.parameter = hidCode;
                }
                else if (
                    hidCode == KEY_LEFTCTRL || hidCode == KEY_LEFTSHIFT ||
                    hidCode == KEY_LEFTALT || hidCode == KEY_LEFTMETA ||
                    hidCode == KEY_RIGHTCTRL || hidCode == KEY_RIGHTSHIFT ||
                    hidCode == KEY_RIGHTALT || hidCode == KEY_RIGHTMETA) {
                    action.type = ActionType::MODIFIER_PRESS;
                    action.parameter = hidCode;
                }
                else {
                    action.type = ActionType::NORMAL_KEY;
                    action.parameter = hidCode;
                }
            }
            else {
                // Assume it's text
                action.type = ActionType::TEXT;
                strncpy(action.text, actionToken, sizeof(action.text) - 1);
                action.text[sizeof(action.text) - 1] = '\0';
            }

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

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

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

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

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

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

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

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

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

    /**
     * @brief Detects touch events.
     * @param pinDetected Pin number that was touched.
     * @param touched Boolean flag indicating if a touch was detected.
     */
    void detectTouch(uint8_t& pinDetected, bool& touched) {
        touched = false;
        pinDetected = 0;

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

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

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

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

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

/**
 * @class KeyboardManager
 * @brief Manages keyboard actions and operations.
 */
class KeyboardManager {
public:
    /**
     * @brief Constructor for KeyboardManager.
     * @param kbInput Pointer to the KeyboardInput instance.
     */
    KeyboardManager(KeyboardInput* kbInput) : keyboardInput(kbInput) {}

    /**
     * @brief Executes the actions defined in a gesture.
     * @param gesture Gesture containing the actions to execute.
     */
    void executeGestureActions(const Gesture& gesture) {
        keyboardExecutor->executeActions(gesture);
    }

    /**
     * @brief Sets the GestureExecutor instance.
     * @param executor Pointer to the GestureExecutor instance.
     */
    void setExecutor(GestureExecutor* executor) {
        keyboardExecutor = executor;
    }

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

/**
 * @class GestureComparator
 * @brief Compares gesture sequences efficiently using Knuth-Morris-Pratt (KMP) algorithm.
 */
class GestureComparator {
public:
    /**
     * @brief Compares two sequences using KMP algorithm.
     * @param haystack Pointer to the first sequence.
     * @param haystackLen Length of the first sequence.
     * @param needle Pointer to the second sequence.
     * @param needleLen Length of the second sequence.
     * @return True if the needle sequence is found in haystack, false otherwise.
     */
    bool compareSequencesKMP(const uint8_t* haystack, uint8_t haystackLen, const uint8_t* needle, uint8_t needleLen) {
        if (needleLen == 0 || haystackLen < needleLen) return false;

        // Compute the Longest Prefix Suffix (LPS) array
        computeLPSArray(needle, needleLen, lps);

        uint8_t i = 0; // Index for haystack
        uint8_t j = 0; // Index for needle

        while (i < haystackLen) {
            if (needle[j] == haystack[i]) {
                i++;
                j++;
                if (j == needleLen) {
                    return true; // Match found
                }
            }
            else {
                if (j != 0) {
                    j = lps[j - 1];
                }
                else {
                    i++;
                }
            }
        }

        return false; // No match found
    }

private:
    uint8_t lpsBuffer[TOUCH_BUFFER_SIZE]; ///< Buffer to store LPS array

    /**
     * @brief Computes the Longest Prefix Suffix (LPS) array for KMP algorithm.
     * @param needle Pointer to the needle sequence.
     * @param needleLen Length of the needle sequence.
     * @param lps Pointer to the buffer to store LPS values.
     */
    void computeLPSArray(const uint8_t* needle, uint8_t needleLen, uint8_t* lps) {
        uint8_t length = 0;
        lps[0] = 0; // LPS[0] is always 0

        for (uint8_t i = 1; i < needleLen; i++) {
            while (length > 0 && needle[i] != needle[length]) {
                length = lps[length - 1];
            }
            if (needle[i] == needle[length]) {
                length++;
            }
            lps[i] = length;
        }
    }
};

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

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

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

/**
 * @class GestureManager
 * @brief Manages gestures including adding, deleting, and listing.
 * @note This class adheres to the Single Responsibility Principle by delegating storage and comparison to other classes.
 */
class GestureManagerSRP {
public:
    /**
     * @brief Constructor for GestureManagerSRP.
     * @param storage Pointer to the GestureStorage instance.
     * @param comparator Pointer to the GestureComparator instance.
     */
    GestureManagerSRP(GestureStorage* storage, GestureComparator* comparator)
        : gestureStorage(storage), gestureComparator(comparator) {}

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

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

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

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

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

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

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

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

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

    /**
     * @brief Lists all stored gestures via Serial.
     */
    void listGestures() const {
        gestureStorage->listGestures();
    }

    /**
     * @brief Finds and executes matching gestures based on the touch buffer.
     * @param touchBuffer Pointer to the touch buffer.
     * @param bufferLen Length of the touch buffer.
     * @param executor Pointer to the GestureExecutor instance.
     */
    void findAndExecuteGesture(const uint8_t* touchBuffer, uint8_t bufferLen, GestureExecutor* executor) {
        gestureStorage->listGestures(); // For debugging purposes
        gestureComparator->compareSequencesKMP(touchBuffer, bufferLen, nullptr, 0); // Placeholder

        // Actual implementation is handled by other classes
    }

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

/**
 * @class BLEManager
 * @brief Manages BLE initialization and connection status.
 */
class BLEManager {
public:
    /**
     * @brief Constructor for BLEManager.
     * @param deviceName Name of the BLE device.
     * @param manufacturer Manufacturer name.
     * @param batteryLevel Initial battery level.
     */
    BLEManager(const std::string& deviceName, const std::string& manufacturer, uint8_t batteryLevel)
        : compositeHID(deviceName, manufacturer, batteryLevel) {}

    /**
     * @brief Initializes BLE and related devices.
     */
    bool initialize() {
        compositeHID.begin();
        Serial.println(F(LOG_PREFIX "BLE initialized successfully."));
        return compositeHID.isConnected();
    }

    /**
     * @brief Adds a device to the BLE composite HID.
     * @param device Pointer to the BaseCompositeDevice instance.
     */
    void addDevice(BaseCompositeDevice* device) {
        compositeHID.addDevice(device);
    }

    /**
     * @brief Checks if BLE is connected.
     * @return True if connected, false otherwise.
     */
    bool isConnected() const {
        return compositeHID.isConnected();
    }

private:
    BleCompositeHID compositeHID; ///< BLE Composite HID instance
};

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

// Initialize Preferences
Preferences preferencesInstance;

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

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

// Initialize Keyboard Device
KeyboardConfiguration keyboardConfig;
keyboardConfig.setAutoReport(true);
KeyboardDevice* keyboard = new KeyboardDevice(keyboardConfig);
if (keyboard == nullptr) {
    #error "Failed to allocate KeyboardDevice."
}

// Initialize BLE Manager
BLEManager bleManager(DEVICE_NAME, DEVICE_MANUFACTURER, 50);

// Initialize Keyboard Input and Executor
KeyboardInput keyboardInput(keyboard);
GestureExecutor gestureExecutor(&keyboardInput);

// Initialize Gesture Executor with KeyboardInput
gestureExecutor.executeActions(Gesture()); // Placeholder

// Initialize Keyboard Manager
KeyboardManager keyboardManager(&keyboardInput);
keyboardManager.setExecutor(&gestureExecutor);

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

// Initialize Touch Handler
TouchHandler touchHandler;

// Initialize Gesture Comparator
// GestureComparator is already initialized above.

// Initialize Gesture Executor
// GestureExecutor is already initialized above.

// Initialize Gesture Manager's Comparator
gestureManager.setComparator(&gestureComparator);

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

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

/**
 * @brief Clears the global touch buffer.
 * @param bufferStart Reference to the buffer start index.
 * @param bufferSize Reference to the buffer size.
 */
void clearBuffer(uint16_t& bufferStart, uint16_t& bufferSize) {
    bufferStart = 0;
    bufferSize = 0;
    Serial.println(F(LOG_PREFIX "Touch buffer cleared."));
}

/**
 * @brief Executes matching gestures based on the touch buffer.
 * @param touchBuffer Pointer to the touch buffer.
 * @param bufferStart Current start index of the buffer.
 * @param bufferSize Current size of the buffer.
 * @param gestureManager Pointer to the GestureManager instance.
 * @param gestureExecutor Pointer to the GestureExecutor instance.
 */
void executeMatchingGesture(const uint8_t* touchBuffer, uint16_t bufferStart, uint16_t bufferSize, GestureManagerSRP* gestureManager, GestureExecutor* gestureExecutor) {
    uint8_t gestureCount = gestureManager->gestureStorage->getGestureCount();
    if (gestureCount == 0) {
        Serial.println(F(LOG_PREFIX "No gestures stored."));
        return;
    }

    // Prepare the buffer slice to compare
    uint8_t relevantBuffer[TOUCH_BUFFER_SIZE];
    uint8_t relevantSize = (bufferSize < TOUCH_BUFFER_SIZE) ? bufferSize : TOUCH_BUFFER_SIZE;
    for (uint8_t i = 0; i < relevantSize; i++) {
        relevantBuffer[i] = touchBuffer[(bufferStart + bufferSize - relevantSize + i) % TOUCH_BUFFER_SIZE];
    }

    // Iterate through all gestures to find matches
    Gesture gesture;
    GestureComparator comparator;
    bool gestureFound = false;
    for (uint8_t i = 0; i < gestureCount; i++) {
        if (!gestureManager->gestureStorage->loadGesture(i, gesture)) {
            Serial.println(F(LOG_PREFIX "Failed to load gesture."));
            continue;
        }

        if (comparator.compareSequencesKMP(relevantBuffer, relevantSize, gesture.pinSequence, gesture.length)) {
            Serial.print(F(LOG_PREFIX "Gesture matched with ID: "));
            Serial.println(gesture.id);

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

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

    // Clear the touch buffer after matching
    clearBuffer(bufferStart, bufferSize);
}

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

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

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

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

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

    // Initialize BLE
    if (!bleManager.initialize()) {
        Serial.println(F(LOG_PREFIX "BLE initialization failed."));
        // Handle error appropriately, possibly retry or enter a safe state
    }

    // Attach KeyboardDevice to BLE
    bleManager.addDevice(keyboard);

    // Initialize KeyboardInput and GestureExecutor
    KeyboardInput keyboardInput(keyboard);
    GestureExecutor gestureExecutor(&keyboardInput);
    keyboardManager.setExecutor(&gestureExecutor);

    // Initialize Gesture Manager
    // Gestures are managed via GestureManagerSRP

    // Initialize Touch Handler
    touchHandler.initialize();

    // Initialize Serial Command Handler
    // Serial commands can now manage gestures via GestureManager

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

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

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

/**
 * @brief Main loop handling touch detection and serial commands.
 */
void loop() {
    static uint8_t touchBufferGlobal[TOUCH_BUFFER_SIZE];
    static uint16_t bufferStart = 0;
    static uint16_t bufferSize = 0;
    static unsigned long lastTouchTime = 0;

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

    unsigned long currentTime = millis();

    if (touched) {
        appendToBuffer(touchedPin, touchBufferGlobal, bufferStart, bufferSize);
        lastTouchTime = currentTime;
    }

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

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

    // Optional: Implement periodic tasks or additional functionalities here
}
```