**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.**
### THIS IS MY CODE:
```
// ========================
// librerias y definiciones
// ========================
#include <Arduino.h>
#include <BleCompositeHID.h>
#include <KeyboardDevice.h>
#include <Preferences.h>
#include <KeyboardHIDCodes.h>

// Forward declarations
class GestureManager;
class GestureExecutor;
class SerialCommandHandler;

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

// Configuration Constants

// Device Information
constexpr char DEVICE_NAME[] PROGMEM = "SIR DEVICE";
constexpr char DEVICE_MANUFACTURER[] PROGMEM = "Los 13 Primes";

// 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 PROGMEM = LED_BUILTIN;
constexpr unsigned long LED_INACTIVITY_THRESHOLD_MS = 200;

// ========================
// Enumerations and Structures
// ========================

/**
 * @enum ActionType
 * @brief Represents the type of HID action to execute.
 */
enum class ActionType {
  NORMAL_KEY = 0,  ///< Standard keyboard key
  MODIFIER_KEY,    ///< Modifier key (press and release)
  TEXT             ///< Text input
};

/**
 * @struct Action
 * @brief Represents an individual HID action.
 */
struct Action {
  ActionType type;   ///< Type of action
  String parameter;  ///< HID code or text

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

/**
 * @struct Gesture
 * @brief Represents a user-defined gesture.
 */
struct Gesture {
  uint8_t id;                        ///< Unique identifier for the gesture
  std::vector<uint8_t> pinSequence;  ///< Sequence of touch pins
  std::vector<Action> actions;       ///< Actions associated with the gesture

  Gesture()
    : id(0) {}
};

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


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

/**
 * @brief Retrieves the HID code for a given action string.
 *        Performs a case-sensitive search.
 * @param action Action string.
 * @return HID code if found, 0 otherwise.
 */
// Modificar la función getHIDCode para eliminar la búsqueda en HIDMap
uint8_t getHIDCode(const String& action) {
  if (action.length() == 0) return 0;

  // Directamente mapear a partir de KeyboardHIDCodes.h sin búsqueda en HIDMap
  if (action == "SPACE") return KEY_SPACE;
  if (action == "ENTER") return KEY_ENTER;
  if (action == "ESCAPE") return KEY_ESC;
  if (action == "BACKSPACE") return KEY_BACKSPACE;
  if (action == "TAB") return KEY_TAB;
  if (action == "CAPSLOCK") return KEY_CAPSLOCK;

  // Modifier Keys
  if (action == "LCTRL") return KEY_LEFTCTRL;
  if (action == "LSHIFT") return KEY_LEFTSHIFT;
  if (action == "LALT") return KEY_LEFTALT;
  if (action == "LGUI") return KEY_LEFTMETA;
  if (action == "RCTRL") return KEY_RIGHTCTRL;
  if (action == "RSHIFT") return KEY_RIGHTSHIFT;
  if (action == "RALT") return KEY_RIGHTALT;
  if (action == "RGUI") return KEY_RIGHTMETA;

  // Arrow Keys
  if (action == "UP") return KEY_UP;
  if (action == "DOWN") return KEY_DOWN;
  if (action == "LEFT") return KEY_LEFT;
  if (action == "RIGHT") return KEY_RIGHT;

  // Numbers
  if (action == "0") return KEY_0;
  if (action == "1") return KEY_1;
  if (action == "2") return KEY_2;
  if (action == "3") return KEY_3;
  if (action == "4") return KEY_4;
  if (action == "5") return KEY_5;
  if (action == "6") return KEY_6;
  if (action == "7") return KEY_7;
  if (action == "8") return KEY_8;
  if (action == "9") return KEY_9;

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

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

  return 0;  // Action not found
}

/**
 * @brief Determines if a character requires the Shift key.
 * @param c Character to check.
 * @return True if Shift is required, false otherwise.
 */
bool needsShift(char c) {
  // Check for uppercase letters
  if (isupper(c)) return true;

  // Symbols that require Shift
  const char symbolsRequiringShift[] = "!@#$%^&*()_+{}|:\"<>?~";
  if (strchr(symbolsRequiringShift, c) != nullptr) return true;

  return false;
}

/**
 * @brief Maps a character to its HID code.
 * @param c Character to map.
 * @return HID code if found, 0 otherwise.
 */
uint8_t charToHID(char c) {
  String keyStr = String(c);
  uint8_t hidCode = getHIDCode(keyStr);
  if (hidCode != 0) return hidCode;

  // Handle lowercase letters
  if (islower(c)) {
    keyStr.toUpperCase();
    return getHIDCode(keyStr);
  }

  // Handle special cases
  switch (c) {
    case ' ':
      return KEY_SPACE;
    case '\n':
    case '\r':
      return KEY_ENTER;
    case '\t':
      return KEY_TAB;
    default:
      return 0;
  }
}

// ========================
// Class Definitions
// ========================

/**
 * @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) {
    String idKey = "g" + String(index) + "_id";
    if (!preferences.isKey(idKey.c_str())) return false;

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

    // Load pin sequence
    String seqKey = "g" + String(index) + "_seq";
    String sequenceStr = preferences.getString(seqKey.c_str(), "");
    gesture.pinSequence.clear();

    int start = 0;
    while (start < sequenceStr.length()) {
      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.push_back(pin);
      }

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

    // Load actions
    String actKey = "g" + String(index) + "_acts";
    String actionsStr = preferences.getString(actKey.c_str(), "");
    gesture.actions.clear();
    start = 0;

    while (start < actionsStr.length()) {
      int semicolon = actionsStr.indexOf(';', start);
      String actionStr = (semicolon == -1) ? actionsStr.substring(start) : actionsStr.substring(start, semicolon);
      actionStr.trim();

      if (actionStr.length() > 0) {
        Action action;

        if (actionStr.startsWith("\"") && actionStr.endsWith("\"")) {
          // Text action
          action.type = ActionType::TEXT;
          action.parameter = actionStr.substring(1, actionStr.length() - 1);
        } else {
          // HID code action
          uint8_t hidCode = getHIDCode(actionStr);

          if (hidCode != 0) {
            action.type = ActionType::NORMAL_KEY;
            action.parameter = actionStr;
          } else {
            // Modifier key action
            hidCode = getHIDCode(actionStr);
            if (hidCode != 0) {
              action.type = ActionType::MODIFIER_KEY;
              action.parameter = actionStr;
            } else {
              // Invalid action
              Serial.println(F(LOG_PREFIX "Invalid action found in gesture."));
              continue;
            }
          }
        }

        // Add action to gesture
        gesture.actions.push_back(action);
      }

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

    // Additional validations
    return (!gesture.pinSequence.empty()) && (!gesture.actions.empty());
  }

  /**
   * @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
    String idKey = "g" + String(index) + "_id";
    if (!preferences.putUInt(idKey.c_str(), gesture.id)) {
      Serial.println(F(LOG_PREFIX "Failed to save gesture ID."));
      return false;
    }

    // Save pin sequence
    String seqKey = "g" + String(index) + "_seq";
    String seqBuffer = "";
    for (size_t j = 0; j < gesture.pinSequence.size(); j++) {
      seqBuffer += String(gesture.pinSequence[j]);
      if (j < gesture.pinSequence.size() - 1) {
        seqBuffer += ",";
      }
    }
    if (!preferences.putString(seqKey.c_str(), seqBuffer)) {
      Serial.println(F(LOG_PREFIX "Failed to save gesture pin sequence."));
      return false;
    }

    // Save actions
    String actKey = "g" + String(index) + "_acts";
    String actsBuffer = "";
    for (size_t a = 0; a < gesture.actions.size(); a++) {
      if (a > 0) actsBuffer += ";";
      if (gesture.actions[a].type == ActionType::TEXT) {
        actsBuffer += "\"" + gesture.actions[a].parameter + "\"";
      } else {
        actsBuffer += gesture.actions[a].parameter;
      }
    }
    if (!preferences.putString(actKey.c_str(), 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
    String idKey = "g" + String(index) + "_id";
    String seqKey = "g" + String(index) + "_seq";
    String actKey = "g" + String(index) + "_acts";
    preferences.remove(idKey.c_str());
    preferences.remove(seqKey.c_str());
    preferences.remove(actKey.c_str());
    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.
 */
class GestureComparator {
public:
  /**
   * @brief Compares two sequences.
   * @param haystack First sequence.
   * @param needle Second sequence.
   * @return True if the needle sequence is found at the end of haystack, false otherwise.
   */
  bool compareSequences(const std::vector<uint8_t>& haystack, const std::vector<uint8_t>& needle) {
    if (needle.empty() || haystack.size() < needle.size()) return false;

    auto it = haystack.end() - needle.size();
    return std::equal(needle.begin(), needle.end(), it);
  }
};

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

  /**
   * @brief Sends an action based on the Action object.
   * @param action The Action to execute.
   */
  void sendAction(const Action& action) {
    switch (action.type) {
      case ActionType::NORMAL_KEY:
        pressAndReleaseKey(action.parameter);
        break;
      case ActionType::MODIFIER_KEY:
        pressAndReleaseModifier(action.parameter);
        break;
      case ActionType::TEXT:
        sendText(action.parameter);
        break;
      default:
        Serial.println(F(LOG_PREFIX "Unknown action type."));
        break;
    }
  }

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

  /**
   * @brief Presses and releases a normal key.
   * @param keyStr Key string to press.
   */
  void pressAndReleaseKey(const String& keyStr) {
    uint8_t keyCode = getHIDCode(keyStr);
    if (keyCode == 0) {
      Serial.print(F(LOG_PREFIX "Invalid key code for key: "));
      Serial.println(keyStr);
      return;
    }

    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 modifier key.
   * @param modifierStr Modifier key string.
   */
  void pressAndReleaseModifier(const String& modifierStr) {
    uint8_t modifierCode = getHIDCode(modifierStr);
    if (modifierCode == 0) {
      Serial.print(F(LOG_PREFIX "Invalid modifier code for key: "));
      Serial.println(modifierStr);
      return;
    }

    Serial.print(F(LOG_PREFIX "Pressing modifier key code: 0x"));
    Serial.println(modifierCode, HEX);

    keyboard->modifierKeyPress(modifierCode);
    delay(KEY_PRESS_DELAY_MS);
    keyboard->modifierKeyRelease(modifierCode);
  }

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

      Serial.print(F(LOG_PREFIX "Character: "));
      Serial.print(currentChar);
      Serial.print(F(" HID Code: 0x"));
      Serial.println(key, HEX);

      if (key != 0) {
        if (shiftNeeded) {
          Serial.println(F(LOG_PREFIX "Shift needed."));
          keyboard->modifierKeyPress(KEY_LEFTSHIFT);
        }

        Serial.print(F(LOG_PREFIX "Pressing key code: 0x"));
        Serial.println(key, HEX);
        keyboard->keyPress(key);
        delay(KEY_PRESS_DELAY_MS);
        keyboard->keyRelease(key);
        Serial.print(F(LOG_PREFIX "Released key code: 0x"));
        Serial.println(key, HEX);

        if (shiftNeeded) {
          Serial.println(F(LOG_PREFIX "Releasing Shift."));
          keyboard->modifierKeyRelease(KEY_LEFTSHIFT);
        }
      } else {
        Serial.print(F(LOG_PREFIX "Failed to get HID code for character: "));
        Serial.println(currentChar);
      }

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

/**
 * @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 (const auto& action : gesture.actions) {
      keyboardInput->sendAction(action);
    }
  }

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

/**
 * @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 (size_t j = 0; j < gesture.pinSequence.size(); j++) {
        Serial.print(gesture.pinSequence[j]);
        if (j < gesture.pinSequence.size() - 1) {
          Serial.print(F(" -> "));
        }
      }

      Serial.print(F(" | Actions: "));
      for (size_t a = 0; a < gesture.actions.size(); a++) {
        const Action& action = gesture.actions[a];
        String actionType;
        switch (action.type) {
          case ActionType::NORMAL_KEY: actionType = "Key"; break;
          case ActionType::MODIFIER_KEY: actionType = "Modifier Key"; break;
          case ActionType::TEXT: actionType = "Text"; break;
          default: actionType = "Unknown"; break;
        }
        if (action.type != ActionType::TEXT) {
          Serial.print(actionType + "(" + action.parameter + ")");
        } else {
          Serial.print(actionType + "(\"" + action.parameter + "\")");
        }
        if (a < gesture.actions.size() - 1) {
          Serial.print(F(", "));
        }
      }
      Serial.println();  // New line after each gesture
    }
  }

  /**
   * @brief Finds and executes matching gestures based on the touch buffer.
   * @param touchBuffer Reference to the touch buffer.
   * @param executor Pointer to the GestureExecutor instance.
   */
  void findAndExecuteGestures(const std::vector<uint8_t>& touchBuffer, 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->compareSequences(touchBuffer, gesture.pinSequence)) {
        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 Retrieves the total number of stored gestures.
   * @return Number of gestures.
   */
  uint8_t getGestureCount() const {
    return gestureStorage->getGestureCount();
  }

  /**
   * @brief Load a gesture by its index.
   * @param index Index of the gesture.
   * @param gesture Reference to store the loaded gesture.
   * @return True if loaded successfully, false otherwise.
   */
  bool loadGesture(uint8_t index, Gesture& gesture) const {
    return gestureStorage->loadGesture(index, gesture);
  }

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()) {
      String commandLine = Serial.readStringUntil('\n');
      commandLine.trim();

      if (commandLine.length() == 0) {
        logMessage(F("Empty command."));
        return;
      }

      int spaceIndex = commandLine.indexOf(' ');
      String command = (spaceIndex == -1) ? commandLine : commandLine.substring(0, spaceIndex);
      String params = (spaceIndex == -1) ? "" : commandLine.substring(spaceIndex + 1);

      command.toUpperCase();

      if (command == "ADD") {
        addCommandGesture(params);
      } else if (command == "DEL") {
        delCommandGesture(params);
      } else if (command == "LIST") {
        gestureManager->listGestures();
      } else if (command == "TEST") {
        logMessage("Free memory: " + String(ESP.getFreeHeap()) + " bytes");
      } else if (command == "HELP") {
        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 String& 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(const String& params) {
    int delimiterIndex = params.indexOf('|');
    if (delimiterIndex == -1) {
      logMessage(F("Invalid ADD format. Missing '|'. Usage: ADD <pin_sequence>|<actions>"));
      return;
    }

    String sequenceStr = params.substring(0, delimiterIndex);
    String actionsStr = params.substring(delimiterIndex + 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
    sequenceStr.trim();
    std::vector<String> pinTokens = splitString(sequenceStr, ' ');
    for (const auto& pinToken : pinTokens) {
      uint8_t pin = static_cast<uint8_t>(pinToken.toInt());
      if (isValidPin(pin)) {
        newGesture.pinSequence.push_back(pin);
      } else {
        logMessage(String(F("Invalid pin ignored in sequence: ")) + pinToken);
      }
    }

    if (newGesture.pinSequence.empty()) {
      logMessage(F("No valid pins added to gesture."));
      return;
    }

    // Process actions
    actionsStr.trim();
  std::vector<String> actionTokens = splitActions(actionsStr);
    for (const auto& actionToken : actionTokens) {
      Action action;
      String trimmedToken = actionToken;
      trimmedToken.trim();

      if (trimmedToken.startsWith("\"") && trimmedToken.endsWith("\"")) {
        // Text action
        action.type = ActionType::TEXT;
        action.parameter = trimmedToken.substring(1, trimmedToken.length() - 1);
      } else {
        // Convert action token to HID code
        uint8_t hidCode = getHIDCode(trimmedToken);

        if (hidCode != 0) {
          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_KEY;
          } else {
            action.type = ActionType::NORMAL_KEY;
          }
          action.parameter = trimmedToken;
        } else {
          logMessage(String(F("Invalid action ignored: ")) + trimmedToken);
          continue;
        }
      }

      newGesture.actions.push_back(action);
    }

    if (newGesture.actions.empty()) {
      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(const String& idStr) {
    uint8_t id = static_cast<uint8_t>(idStr.toInt());
    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->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 = 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"));
  }

  /**
   * @brief Splits a string by a delimiter.
   * @param str The string to split.
   * @param delimiter The delimiter character.
   * @return A vector of substrings.
   */
  std::vector<String> splitString(const String& str, char delimiter) {
    std::vector<String> tokens;
    int start = 0;
    int end = str.indexOf(delimiter);
    while (end != -1) {
      tokens.push_back(str.substring(start, end));
      start = end + 1;
      end = str.indexOf(delimiter, start);
    }
    tokens.push_back(str.substring(start));
    return tokens;
  }

  /**
   * @brief Splits actions string into tokens, handling quoted text.
   * @param actionsStr The actions string.
   * @return A vector of action tokens.
   */
  std::vector<String> splitActions(const String& actionsStr) {
    std::vector<String> tokens;
    bool inQuotes = false;
    String currentToken = "";
    for (size_t i = 0; i < actionsStr.length(); i++) {
      char ch = actionsStr[i];
      if (ch == '\"') {
        inQuotes = !inQuotes;
        currentToken += ch;
      } else if (ch == ';' && !inQuotes) {
        tokens.push_back(currentToken);
        currentToken = "";
      } else {
        currentToken += ch;
      }
    }
    if (currentToken.length() > 0) {
      tokens.push_back(currentToken);
    }
    return tokens;
  }
};

/**
 * @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 = TOUCH_PINS[i];
      touchAttachInterrupt(pin, nullptr, TOUCH_THRESHOLDS[i]);
    }
    pinMode(LED_PIN, OUTPUT);    // Configure built-in LED as output
    digitalWrite(LED_PIN, LOW);  // Turn off LED initially
  }

  /**
   * @brief Detects touch events.
   * @param touchedPin Pin number that was touched.
   * @return True if a touch was detected.
   */
  bool detectTouch(uint8_t& touchedPin) {
    for (uint8_t i = 0; i < NUM_TOUCH_PINS; i++) {
      uint8_t pin = TOUCH_PINS[i];
      uint8_t threshold = TOUCH_THRESHOLDS[i];
      bool isTouched = touchRead(pin) < threshold;

      // Detect change from not touched to touched
      if (isTouched && !(previousTouchState & (1 << i))) {
        touchedPin = pin;
        previousTouchState |= (1 << i);  // Mark as touched
        setLEDState(true);
        lastActivityTime = millis();
        return true;
      } else if (!isTouched && (previousTouchState & (1 << i))) {
        previousTouchState &= ~(1 << i);  // Mark as not touched
      }
    }

    // Turn off LED if inactivity exceeds threshold
    if (millis() - lastActivityTime > LED_INACTIVITY_THRESHOLD_MS) {
      setLEDState(false);
    }

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

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

/**
 * @brief Appends a pin to the touch buffer.
 * @param pin Pin number to add.
 * @param touchBuffer Reference to the touch buffer.
 */
void appendToBuffer(uint8_t pin, std::vector<uint8_t>& touchBuffer) {
  if (touchBuffer.size() >= TOUCH_BUFFER_SIZE) {
    touchBuffer.erase(touchBuffer.begin());  // Remove oldest
  }
  touchBuffer.push_back(pin);
}

/**
 * @brief Clears the touch buffer.
 * @param touchBuffer Reference to the touch buffer.
 */
void clearBuffer(std::vector<uint8_t>& touchBuffer) {
  touchBuffer.clear();
  Serial.println(F(LOG_PREFIX "Touch buffer cleared."));
}

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

// Initialize Preferences
Preferences preferencesInstance;

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

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

// Initialize Keyboard Device and Configuration
KeyboardDevice* keyboard = nullptr;  // Pointer to be initialized in setup()

// Initialize BLE Manager
BleCompositeHID compositeHID(DEVICE_NAME, DEVICE_MANUFACTURER, 50);

// Initialize Keyboard Input and Executor
KeyboardInput keyboardInput(nullptr);  // Will set the keyboard instance in setup()
GestureExecutor gestureExecutor(&keyboardInput);

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

// Initialize Touch Handler
TouchHandler touchHandler;

// ========================
// 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 Keyboard Device and Configuration
  keyboard = new KeyboardDevice();

  if (keyboard == nullptr) {
    Serial.println(F(LOG_PREFIX "Failed to allocate KeyboardDevice. Entering safe state."));
    while (1)
      ;  // Enter safe state
  }

  // Update keyboard input with the initialized keyboard
  keyboardInput = KeyboardInput(keyboard);

  // Initialize BLE
  compositeHID.addDevice(keyboard);
  compositeHID.begin();
  Serial.println(F(LOG_PREFIX "BLE initialized."));

  // Initialize Touch Handler
  touchHandler.initialize();

  // 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 std::vector<uint8_t> touchBuffer;
  static unsigned long lastTouchTime = 0;

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

  unsigned long currentTime = millis();

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

  // If inactivity exceeds timeout, verify gestures
  if (!touchBuffer.empty() && (currentTime - lastTouchTime > INACTIVITY_TIMEOUT_MS)) {
    gestureManager.findAndExecuteGestures(touchBuffer, &gestureExecutor);
    clearBuffer(touchBuffer);
  }

  // Handle serial commands
  if (Serial.available()) {
    serialHandler.handleCommands();
  }
}
```
### NOTES AND EXAMPLES:
```
├── .vscode
    ├── arduino.json
    ├── c_cpp_properties.json
    └── settings.json
├── BLEHostConfiguration.cpp
├── BLEHostConfiguration.h
├── BaseCompositeDevice.cpp
├── BaseCompositeDevice.h
├── BleCompositeHID.cpp
├── BleCompositeHID.h
├── BleConnectionStatus.cpp
├── BleConnectionStatus.h
├── GamepadConfiguration.cpp
├── GamepadConfiguration.h
├── GamepadDevice.cpp
├── GamepadDevice.h
├── KeyboardConfiguration.cpp
├── KeyboardConfiguration.h
├── KeyboardDescriptors.h
├── KeyboardDevice.cpp
├── KeyboardDevice.h
├── KeyboardHIDCodes.h
├── MouseConfiguration.cpp
├── MouseConfiguration.h
├── MouseDevice.cpp
├── MouseDevice.h
├── README.md
├── SafeQueue.hpp
├── XboxDescriptors.h
├── XboxGamepadConfiguration.h
├── XboxGamepadConfigurations.cpp
├── XboxGamepadDevice.cpp
├── XboxGamepadDevice.h
├── examples
    ├── GamepadExamples
    │   ├── CharacteristicsConfiguration
    │   │   └── CharacteristicsConfiguration.ino
    │   ├── DrivingControllerTest
    │   │   └── DrivingControllerTest.ino
    │   ├── FlightControllerTest
    │   │   └── FlightControllerTest.ino
    │   ├── Gamepad
    │   │   └── Gamepad.ino
    │   ├── IndividualAxes
    │   │   └── IndividualAxes.ino
    │   ├── Keypad4x4
    │   │   └── Keypad4x4.ino
    │   ├── MultipleButtons
    │   │   └── MultipleButtons.ino
    │   ├── MultipleButtonsAndHats
    │   │   └── MultipleButtonsAndHats.ino
    │   ├── MultipleButtonsDebounce
    │   │   └── MultipleButtonsDebounce.ino
    │   ├── PlayerIndicators
    │   │   └── PlayerIndicators.ino
    │   ├── PotAsAxis
    │   │   └── PotAsAxis.ino
    │   ├── SingleButton
    │   │   └── SingleButton.ino
    │   ├── SingleButtonDebounce
    │   │   └── SingleButtonDebounce.ino
    │   ├── SpecialButtons
    │   │   └── SpecialButtons.ino
    │   ├── TestAll
    │   │   └── TestAll.ino
    │   └── XboxXInputController
    │   │   └── XboxXInputController.ino
    ├── KeyboardExamples
    │   ├── KeyboardLEDs
    │   │   └── KeyboardLEDs.ino
    │   ├── MediaKeys
    │   │   └── MediaKeys.ino
    │   └── PressKey
    │   │   └── PressKey.ino
    ├── MultipleDeviceExamples
    │   ├── DeferredReports
    │   │   └── DeferredReports.ino
    │   ├── GamepadAndMouse
    │   │   └── GamepadAndMouse.ino
    │   ├── GamepadMouseKeyboard
    │   │   └── GamepadMouseKeyboard.ino
    │   └── MouseAndKeyboard
    │   │   └── MouseAndKeyboard.ino
    └── SetBatteryLevel
    │   └── SetBatteryLevel.ino
├── keywords.txt
├── library.properties
├── license.txt
├── notes
    └── packet_logs.txt
└── scripts
    └── parse-descriptor.py


/.vscode/arduino.json:
--------------------------------------------------------------------------------
1 | {
2 |     "sketch": "examples\\GamepadAndMouse\\GamepadAndMouse.ino",
3 |     "configuration": "PartitionScheme=default,FlashFreq=80,UploadSpeed=921600,DebugLevel=debug,EraseFlash=none",
4 |     "board": "esp32:esp32:d32",
5 |     "output": "../build",
6 |     "port": "COM6"
7 | }


--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "files.associations": {
 3 |         "initializer_list": "cpp",
 4 |         "vector": "cpp",
 5 |         "xutility": "cpp",
 6 |         "xstring": "cpp",
 7 |         "sstream": "cpp",
 8 |         "ios": "cpp",
 9 |         "iomanip": "cpp",
10 |         "type_traits": "cpp",
11 |         "atomic": "cpp",
12 |         "bit": "cpp",
13 |         "cctype": "cpp",
14 |         "clocale": "cpp",
15 |         "cmath": "cpp",
16 |         "compare": "cpp",
17 |         "concepts": "cpp",
18 |         "cstddef": "cpp",
19 |         "cstdint": "cpp",
20 |         "cstdio": "cpp",
21 |         "cstdlib": "cpp",
22 |         "cstring": "cpp",
23 |         "ctime": "cpp",
24 |         "cwchar": "cpp",
25 |         "exception": "cpp",
26 |         "iosfwd": "cpp",
27 |         "iostream": "cpp",
28 |         "istream": "cpp",
29 |         "iterator": "cpp",
30 |         "limits": "cpp",
31 |         "memory": "cpp",
32 |         "new": "cpp",
33 |         "ostream": "cpp",
34 |         "stdexcept": "cpp",
35 |         "streambuf": "cpp",
36 |         "string": "cpp",
37 |         "system_error": "cpp",
38 |         "tuple": "cpp",
39 |         "typeinfo": "cpp",
40 |         "utility": "cpp",
41 |         "xfacet": "cpp",
42 |         "xiosbase": "cpp",
43 |         "xlocale": "cpp",
44 |         "xlocinfo": "cpp",
45 |         "xlocmon": "cpp",
46 |         "xlocnum": "cpp",
47 |         "xloctime": "cpp",
48 |         "xmemory": "cpp",
49 |         "xstddef": "cpp",
50 |         "xtr1common": "cpp"
51 |     }
52 | }


--------------------------------------------------------------------------------
/BLEHostConfiguration.cpp:
--------------------------------------------------------------------------------
 1 | #include "BLEHostConfiguration.h"
 2 | 
 3 | BLEHostConfiguration::BLEHostConfiguration() :
 4 |     _vidSource(0x01),
 5 |     _vid(0xe502),
 6 |     _pid(0xbbab),
 7 |     _guidVersion(0x0110),
 8 |     _modelNumber("1.0.0"),
 9 |     _softwareRevision("1.0.0"),
10 |     _serialNumber("0123456789"),
11 |     _firmwareRevision("1.0.0"),
12 |     _hardwareRevision("1.0.0"),
13 |     _systemID(""),
14 |     _deferSendRate(240),
15 |     _threadedAutoSend(false)
16 | {               
17 | }
18 | 
19 | uint16_t BLEHostConfiguration::getVidSource(){ return _vidSource; }
20 | uint16_t BLEHostConfiguration::getVid(){ return _vid; }
21 | uint16_t BLEHostConfiguration::getPid(){ return _pid; }
22 | uint16_t BLEHostConfiguration::getGuidVersion(){ return _guidVersion; }
23 | 
24 | const char* BLEHostConfiguration::getModelNumber(){ return _modelNumber.c_str(); }
25 | const char* BLEHostConfiguration::getSoftwareRevision(){ return _softwareRevision.c_str(); }
26 | const char* BLEHostConfiguration::getSerialNumber(){ return _serialNumber.c_str(); }
27 | const char* BLEHostConfiguration::getFirmwareRevision(){ return _firmwareRevision.c_str(); }
28 | const char* BLEHostConfiguration::getHardwareRevision(){ return _hardwareRevision.c_str(); }
29 | const char* BLEHostConfiguration::getSystemID(){ return _systemID.c_str(); }
30 | 
31 | void BLEHostConfiguration::setVidSource(uint8_t value) { _vidSource = value; }
32 | void BLEHostConfiguration::setVid(uint16_t value) { _vid = value; }
33 | void BLEHostConfiguration::setPid(uint16_t value) { _pid = value; }
34 | void BLEHostConfiguration::setGuidVersion(uint16_t value) { _guidVersion = value; }
35 | 
36 | void BLEHostConfiguration::setModelNumber(const char *value) { _modelNumber = std::string(value); }
37 | void BLEHostConfiguration::setSoftwareRevision(const char *value) { _softwareRevision = std::string(value); }
38 | void BLEHostConfiguration::setSerialNumber(const char *value) { _serialNumber = std::string(value); }
39 | void BLEHostConfiguration::setFirmwareRevision(const char *value) { _firmwareRevision = std::string(value); }
40 | void BLEHostConfiguration::setHardwareRevision(const char *value) { _hardwareRevision = std::string(value); }
41 | 
42 | void BLEHostConfiguration::setQueueSendRate(uint32_t value) { _deferSendRate = value; }
43 | uint32_t BLEHostConfiguration::getQueueSendRate() const { return _deferSendRate; }
44 | 
45 | void BLEHostConfiguration::setQueuedSending(bool value) { _threadedAutoSend = value; }
46 | bool BLEHostConfiguration::getQueuedSending() const { return _threadedAutoSend; }


--------------------------------------------------------------------------------
/BLEHostConfiguration.h:
--------------------------------------------------------------------------------
 1 | #ifndef ESP32_BLE_HOST_CONFIG_H
 2 | #define ESP32_BLE_HOST_CONFIG_H
 3 | 
 4 | #include <Arduino.h>
 5 | #include <string>
 6 | 
 7 | // VID Source defines. These determine from which vendor pool the VID should be sourced from
 8 | #define VENDOR_BLUETOOTH_SOURCE 0x01
 9 | #define VENDOR_USB_SOURCE 0x02
10 | 
11 | class BLEHostConfiguration
12 | {
13 | private:
14 |     
15 |     uint8_t _vidSource;
16 |     uint16_t _vid;
17 |     uint16_t _pid;
18 | 	uint16_t _guidVersion;
19 |     std::string _modelNumber;
20 |     std::string _softwareRevision;
21 |     std::string _serialNumber;
22 |     std::string _firmwareRevision;
23 |     std::string _hardwareRevision;
24 |     std::string _systemID;
25 | 
26 | public:
27 |     BLEHostConfiguration();
28 |     uint16_t getVidSource();
29 |     uint16_t getVid();
30 |     uint16_t getPid();
31 | 	uint16_t getGuidVersion();
32 |     const char* getModelNumber();
33 |     const char* getSoftwareRevision();
34 |     const char* getSerialNumber();
35 |     const char* getFirmwareRevision();
36 |     const char* getHardwareRevision();
37 |     const char* getSystemID();
38 | 
39 |     void setVidSource(uint8_t value);
40 |     void setVid(uint16_t value);
41 |     void setPid(uint16_t value);
42 | 	void setGuidVersion(uint16_t value);
43 |     void setModelNumber(const char *value);
44 |     void setSoftwareRevision(const char *value);
45 |     void setSerialNumber(const char *value);
46 |     void setFirmwareRevision(const char *value);
47 |     void setHardwareRevision(const char *value);
48 | 
49 |     // Set how quickly the auto-send should send queued reports. 
50 |     // A value of 0 will send reports as soon as they are queued.
51 |     void setQueueSendRate(uint32_t frequency);
52 |     uint32_t getQueueSendRate() const;
53 | 
54 |     void setQueuedSending(bool value);
55 |     bool getQueuedSending() const;
56 | 
57 | private:
58 |     uint32_t _deferSendRate;
59 |     bool _threadedAutoSend;
60 | };
61 | 
62 | #endif
63 | 


--------------------------------------------------------------------------------
/BaseCompositeDevice.cpp:
--------------------------------------------------------------------------------
 1 | #include "BaseCompositeDevice.h"
 2 | #include "BleCompositeHID.h"
 3 | 
 4 | BaseCompositeDeviceConfiguration::BaseCompositeDeviceConfiguration(uint8_t reportId) : 
 5 |     _autoReport(true),
 6 |     _reportId(reportId),
 7 |     _autoDefer(false)
 8 | {
 9 | }
10 | 
11 | const char* BaseCompositeDeviceConfiguration::getDeviceName() const {
12 |     return "BaseCompositeDevice";
13 | }
14 | 
15 | BLEHostConfiguration BaseCompositeDeviceConfiguration::getIdealHostConfiguration() const {
16 |     return BLEHostConfiguration();
17 | }
18 | 
19 | bool BaseCompositeDeviceConfiguration::getAutoReport() const { return _autoReport; }
20 | uint8_t BaseCompositeDeviceConfiguration::getReportId() const { return _reportId; }
21 | 
22 | void BaseCompositeDeviceConfiguration::setAutoReport(bool value) { _autoReport = value; }
23 | void BaseCompositeDeviceConfiguration::setHidReportId(uint8_t value) { _reportId = value; }
24 | 
25 | void BaseCompositeDeviceConfiguration::setAutoDefer(bool value) { _autoDefer = value; }
26 | bool BaseCompositeDeviceConfiguration::getAutoDefer() const { return _autoDefer; }
27 | 
28 | // ---------------
29 | 
30 | void BaseCompositeDevice::queueDeferredReport(std::function<void()> && reportFunc) {
31 |     if(auto parent = getParent()){
32 |         parent->queueDeviceDeferredReport(std::forward<std::function<void()>>(reportFunc));
33 |     }
34 | }
35 | 
36 | BleCompositeHID* BaseCompositeDevice::getParent() { return _parent; }
37 | 
38 | void BaseCompositeDevice::setCharacteristics(NimBLECharacteristic* input, NimBLECharacteristic* output) {
39 |     _input = input;
40 |     _output = output;
41 | }
42 | 
43 | NimBLECharacteristic* BaseCompositeDevice::getInput() { return _input; }
44 | NimBLECharacteristic* BaseCompositeDevice::getOutput() { return _output; }
45 | 


--------------------------------------------------------------------------------
/BaseCompositeDevice.h:
--------------------------------------------------------------------------------
 1 | #ifndef COMPOSITE_CONFIG_H
 2 | #define COMPOSITE_CONFIG_H
 3 | 
 4 | #include <Arduino.h>
 5 | //#include <HIDKeyboardTypes.h>
 6 | #include <NimBLECharacteristic.h>
 7 | #include <NimBLEHIDDevice.h>
 8 | #include "BLEHostConfiguration.h"
 9 | 
10 | // Forwards
11 | class BleCompositeHID;
12 | 
13 | class BaseCompositeDeviceConfiguration 
14 | {
15 | public:
16 |     BaseCompositeDeviceConfiguration(uint8_t reportId);
17 | 
18 |     bool getAutoReport() const;
19 |     void setAutoReport(bool value);
20 | 
21 |     uint8_t getReportId() const;
22 |     void setHidReportId(uint8_t value);
23 |     
24 |     void setAutoDefer(bool value);
25 |     bool getAutoDefer() const;
26 | 
27 |     virtual const char* getDeviceName() const;
28 |     virtual BLEHostConfiguration getIdealHostConfiguration() const;
29 |     virtual uint8_t getDeviceReportSize() const = 0;
30 |     virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const = 0;
31 | 
32 | private:
33 |     bool _autoReport;
34 |     bool _autoDefer;
35 |     uint8_t _reportId;
36 | };
37 | 
38 | 
39 | class BaseCompositeDevice 
40 | {
41 |     friend class BleCompositeHID;
42 | public:
43 |     virtual void init(NimBLEHIDDevice* hid) = 0;
44 |     virtual const BaseCompositeDeviceConfiguration* getDeviceConfig() const = 0;
45 |     
46 |     BleCompositeHID* getParent();
47 | 
48 | protected:
49 |     void queueDeferredReport(std::function<void()> && reportFunc);
50 |     void setCharacteristics(NimBLECharacteristic* input, NimBLECharacteristic* output);
51 |     NimBLECharacteristic* getInput();
52 |     NimBLECharacteristic* getOutput();
53 | 
54 | private:
55 |     BleCompositeHID* _parent;
56 |     NimBLECharacteristic* _input;
57 |     NimBLECharacteristic* _output;
58 | };
59 | 
60 | #endif


--------------------------------------------------------------------------------
/BleCompositeHID.cpp:
--------------------------------------------------------------------------------
  1 | #include "BleCompositeHID.h"
  2 | #include "BleConnectionStatus.h"
  3 | 
  4 | #include <sstream>
  5 | #include <iostream>
  6 | #include <iomanip>
  7 | #include <NimBLEDevice.h>
  8 | #include <NimBLEUtils.h>
  9 | #include <NimBLEServer.h>
 10 | #include <NimBLEHIDDevice.h>
 11 | 
 12 | #if defined(CONFIG_ARDUHAL_ESP_LOG)
 13 | #include "esp32-hal-log.h"
 14 | #define LOG_TAG "BLECompositeHID"
 15 | #else
 16 | #include "esp_log.h"
 17 | static const char *LOG_TAG = "BLECompositeHID";
 18 | #endif
 19 | 
 20 | #define SERVICE_UUID_DEVICE_INFORMATION        "180A"      // Service - Device information
 21 | 
 22 | #define CHARACTERISTIC_UUID_SYSTEM_ID          "2A23"      // Characteristic - System ID 0x2A23
 23 | #define CHARACTERISTIC_UUID_MODEL_NUMBER       "2A24"      // Characteristic - Model Number String - 0x2A24
 24 | #define CHARACTERISTIC_UUID_SOFTWARE_REVISION  "2A28"      // Characteristic - Software Revision String - 0x2A28
 25 | #define CHARACTERISTIC_UUID_SERIAL_NUMBER      "2A25"      // Characteristic - Serial Number String - 0x2A25
 26 | #define CHARACTERISTIC_UUID_FIRMWARE_REVISION  "2A26"      // Characteristic - Firmware Revision String - 0x2A26
 27 | #define CHARACTERISTIC_UUID_HARDWARE_REVISION  "2A27"      // Characteristic - Hardware Revision String - 0x2A27
 28 | 
 29 | uint16_t vidSource;
 30 | uint16_t vid;
 31 | uint16_t pid;
 32 | uint16_t guidVersion;
 33 | std::string modelNumber;
 34 | std::string softwareRevision;
 35 | std::string serialNumber;
 36 | std::string firmwareRevision;
 37 | std::string hardwareRevision;
 38 | std::string systemID;
 39 | 
 40 | std::string uint8_to_hex_string(const uint8_t *v, const size_t s) {
 41 |   std::stringstream ss;
 42 | 
 43 |   ss << std::hex << std::setfill('0');
 44 | 
 45 |   for (int i = 0; i < s; i++) {
 46 |     ss << std::hex << std::setw(2) << static_cast<int>(v[i]);
 47 |   }
 48 | 
 49 |   return ss.str();
 50 | }
 51 | 
 52 | BleCompositeHID::BleCompositeHID(std::string deviceName, std::string deviceManufacturer, uint8_t batteryLevel) : _hid(nullptr)
 53 | {
 54 |     this->deviceName = deviceName.substr(0, CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN - 1);
 55 |     this->deviceManufacturer = deviceManufacturer;
 56 |     this->batteryLevel = batteryLevel;
 57 |     this->_connectionStatus = new BleConnectionStatus();
 58 | }
 59 | 
 60 | BleCompositeHID::~BleCompositeHID()
 61 | {
 62 |     delete this->_connectionStatus;
 63 | }
 64 | 
 65 | void BleCompositeHID::begin()
 66 | {
 67 |     this->begin(BLEHostConfiguration());
 68 | }
 69 | 
 70 | void BleCompositeHID::begin(const BLEHostConfiguration& config)
 71 | {
 72 |     _configuration = config; // we make a copy, so the user can't change actual values midway through operation, without calling the begin function again
 73 | 
 74 |     modelNumber = _configuration.getModelNumber();
 75 |     softwareRevision = _configuration.getSoftwareRevision();
 76 |     serialNumber = _configuration.getSerialNumber();
 77 |     firmwareRevision = _configuration.getFirmwareRevision();
 78 |     hardwareRevision = _configuration.getHardwareRevision();
 79 |     systemID = _configuration.getSystemID();
 80 | 
 81 |     vidSource = _configuration.getVidSource();
 82 | 	vid = _configuration.getVid();
 83 | 	pid = _configuration.getPid();
 84 | 	guidVersion = _configuration.getGuidVersion();
 85 | 
 86 | #ifndef PNPVersionField
 87 |     // Legacy behaviour for versions of Nimble <= 1.4.1
 88 | 	uint8_t high = highByte(vid);
 89 | 	uint8_t low = lowByte(vid);
 90 | 
 91 | 	vid = low << 8 | high;
 92 | 
 93 | 	high = highByte(pid);
 94 | 	low = lowByte(pid);
 95 | 
 96 | 	pid = low << 8 | high;
 97 | 	
 98 | 	high = highByte(guidVersion);
 99 | 	low = lowByte(guidVersion);
100 | 	guidVersion = low << 8 | high;
101 | #endif
102 |     
103 |     // Start BLE server
104 |     xTaskCreate(this->taskServer, "server", 20000, (void *)this, 5, NULL);
105 | }
106 | 
107 | void BleCompositeHID::end(void)
108 | {
109 |     vTaskDelete(this->_autoSendTaskHandle);
110 | }
111 | 
112 | void BleCompositeHID::timedSendDeferredReports(void *pvParameter)
113 | {
114 |     BleCompositeHID *BleCompositeHIDInstance = (BleCompositeHID *)pvParameter;
115 | 
116 |     if (BleCompositeHIDInstance->_hid)
117 |     {
118 |         std::function<void()> reportFunc;
119 |         while(BleCompositeHIDInstance->_deferredReports.ConsumeSync(reportFunc)){
120 |             reportFunc();
121 |             if(BleCompositeHIDInstance->_configuration.getQueueSendRate() > 0)
122 |                 vTaskDelay((1000 / BleCompositeHIDInstance->_configuration.getQueueSendRate()) / portTICK_PERIOD_MS);
123 |         }
124 |     }
125 | }
126 | 
127 | void BleCompositeHID::addDevice(BaseCompositeDevice *device)
128 | {
129 |     device->_parent = this;
130 |     _devices.push_back(device);
131 | }
132 | 
133 | bool BleCompositeHID::isConnected()
134 | {
135 |     return this->_connectionStatus->isConnected();
136 | }
137 | 
138 | void BleCompositeHID::setBatteryLevel(uint8_t level)
139 | {
140 |     this->batteryLevel = level;
141 |     if (this->_hid)
142 |     {
143 |         this->_hid->setBatteryLevel(this->batteryLevel);
144 | 
145 |         if (this->isConnected())
146 |         {
147 |             this->_hid->batteryLevel()->notify();
148 |         }
149 | 		
150 |         // if (this->_configuration.getAutoReport())
151 |         // {
152 |         //     // TODO: Send report?
153 |         // }
154 |     }
155 | }
156 | 
157 | void BleCompositeHID::queueDeviceDeferredReport(std::function<void()> && reportFunc)
158 | {
159 |     this->_deferredReports.Produce(std::forward<std::function<void()>>(reportFunc));
160 | }
161 | 
162 | void BleCompositeHID::sendDeferredReports()
163 | {
164 |     if (this->_hid)
165 |     {
166 |         std::function<void()> reportFunc;
167 |         while(this->_deferredReports.Consume(reportFunc)){
168 |             reportFunc();
169 |         }
170 |     }
171 | }
172 | 
173 | void BleCompositeHID::taskServer(void *pvParameter)
174 | {
175 |     BleCompositeHID *BleCompositeHIDInstance = (BleCompositeHID *)pvParameter; // static_cast<BleCompositeHID *>(pvParameter);
176 | 
177 |     // Use the procedure below to set a custom Bluetooth MAC address
178 |     // Compiler adds 0x02 to the last value of board's base MAC address to get the BT MAC address, so take 0x02 away from the value you actually want when setting
179 |     //uint8_t newMACAddress[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF - 0x02};
180 |     //esp_base_mac_addr_set(&newMACAddress[0]); // Set new MAC address 
181 |     NimBLEDevice::init(BleCompositeHIDInstance->deviceName);
182 |     NimBLEServer *pServer = NimBLEDevice::createServer();
183 |     pServer->setCallbacks(BleCompositeHIDInstance->_connectionStatus);
184 | 
185 |     BleCompositeHIDInstance->_hid = new NimBLEHIDDevice(pServer);
186 |     
187 |     // Setup the HID descriptor buffers
188 |     size_t totalBufferSize = 2048;
189 |     uint8_t tempHidReportDescriptor[totalBufferSize];
190 |     int hidReportDescriptorSize = 0;
191 |     ESP_LOGD(LOG_TAG, "About to init devices");
192 |     
193 |     // Setup child devices to build the HID report descriptor
194 |     for(auto device : BleCompositeHIDInstance->_devices){
195 |         ESP_LOGD(LOG_TAG, "Before device %s init", device->getDeviceConfig()->getDeviceName());
196 |         device->init(BleCompositeHIDInstance->_hid);
197 |         ESP_LOGD(LOG_TAG, "After device %s init", device->getDeviceConfig()->getDeviceName());
198 |         
199 |         auto config = device->getDeviceConfig();
200 |         size_t reportSize = config->makeDeviceReport(tempHidReportDescriptor + hidReportDescriptorSize, totalBufferSize);
201 |         
202 |         if(reportSize >= BLE_ATT_ATTR_MAX_LEN){
203 |             ESP_LOGE(LOG_TAG, "Device report size %d is larger than max buffer size %d", reportSize, BLE_ATT_ATTR_MAX_LEN);
204 |             return;
205 |         } else if(reportSize == 0){
206 |             ESP_LOGE(LOG_TAG, "Device report size is 0");
207 |             return;
208 |         } else if(reportSize < 0){
209 |             ESP_LOGE(LOG_TAG, "Error creating report for device %s", config->getDeviceName());
210 |             return;
211 |         } else {
212 |             ESP_LOGD(LOG_TAG, "Created device %s with report size %d", config->getDeviceName(), reportSize);
213 |         }
214 |         hidReportDescriptorSize += reportSize;
215 |     }
216 |     ESP_LOGD(LOG_TAG, "Final hidReportDescriptorSize: %d", hidReportDescriptorSize);
217 | 
218 |     // Set the report map
219 |     uint8_t customHidReportDescriptor[hidReportDescriptorSize];
220 |     memcpy(&customHidReportDescriptor, tempHidReportDescriptor, hidReportDescriptorSize);
221 |     BleCompositeHIDInstance->_hid->reportMap(&customHidReportDescriptor[0], hidReportDescriptorSize);
222 | 
223 |     // Set manufacturer info
224 |     BleCompositeHIDInstance->_hid->manufacturer()->setValue(BleCompositeHIDInstance->deviceManufacturer);
225 | 
226 |     // Create device UUID
227 |     NimBLEService *pService = pServer->getServiceByUUID(SERVICE_UUID_DEVICE_INFORMATION);
228 | 	
229 |     // Create characteristics
230 | 	BLECharacteristic* pCharacteristic_Model_Number = pService->createCharacteristic(
231 |       CHARACTERISTIC_UUID_MODEL_NUMBER,
232 |       NIMBLE_PROPERTY::READ
233 |     );
234 |     pCharacteristic_Model_Number->setValue(modelNumber);
235 | 	
236 | 	BLECharacteristic* pCharacteristic_Software_Revision = pService->createCharacteristic(
237 |       CHARACTERISTIC_UUID_SOFTWARE_REVISION,
238 |       NIMBLE_PROPERTY::READ
239 |     );
240 |     pCharacteristic_Software_Revision->setValue(softwareRevision);
241 | 	
242 | 	BLECharacteristic* pCharacteristic_Serial_Number = pService->createCharacteristic(
243 |       CHARACTERISTIC_UUID_SERIAL_NUMBER,
244 |       NIMBLE_PROPERTY::READ
245 |     );
246 |     pCharacteristic_Serial_Number->setValue(serialNumber);
247 | 	
248 | 	BLECharacteristic* pCharacteristic_Firmware_Revision = pService->createCharacteristic(
249 |       CHARACTERISTIC_UUID_FIRMWARE_REVISION,
250 |       NIMBLE_PROPERTY::READ
251 |     );
252 |     pCharacteristic_Firmware_Revision->setValue(firmwareRevision);
253 | 	
254 | 	BLECharacteristic* pCharacteristic_Hardware_Revision = pService->createCharacteristic(
255 |       CHARACTERISTIC_UUID_HARDWARE_REVISION,
256 |       NIMBLE_PROPERTY::READ
257 |     );
258 |     pCharacteristic_Hardware_Revision->setValue(hardwareRevision);
259 | 
260 |     // BLECharacteristic* pCharacteristic_System_ID = pService->createCharacteristic(
261 |     //   CHARACTERISTIC_UUID_SYSTEM_ID,
262 |     //   NIMBLE_PROPERTY::READ
263 |     // );
264 |     // pCharacteristic_Hardware_Revision->setValue();
265 | 
266 |     // Set PnP IDs
267 |     BleCompositeHIDInstance->_hid->pnp(vidSource, vid, pid, guidVersion);
268 |     BleCompositeHIDInstance->_hid->hidInfo(0x00, 0x01);
269 | 
270 |     // NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND);  //BLE_SM_PAIR_AUTHREQ_SC
271 | 	NimBLEDevice::setSecurityAuth(true, false, false); // enable bonding, no MITM, no SC
272 | 
273 |     // Start BLE server
274 |     BleCompositeHIDInstance->_hid->startServices();
275 |     BleCompositeHIDInstance->onStarted(pServer);
276 | 
277 |     // Start BLE advertisement
278 |     NimBLEAdvertising *pAdvertising = pServer->getAdvertising();
279 |     pAdvertising->setAppearance(GENERIC_HID);
280 |     pAdvertising->addServiceUUID(BleCompositeHIDInstance->_hid->hidService()->getUUID());
281 |     pAdvertising->start();
282 |     ESP_LOGD(LOG_TAG, "Advertising started!");
283 | 
284 |     // Update battery
285 |     BleCompositeHIDInstance->_hid->setBatteryLevel(BleCompositeHIDInstance->batteryLevel);
286 | 
287 |     // Start timed auto send for deferred reports
288 |     if(BleCompositeHIDInstance->_configuration.getQueuedSending()){
289 |         xTaskCreate(BleCompositeHIDInstance->timedSendDeferredReports, "autoSend", 20000, (void *)BleCompositeHIDInstance, 5, &BleCompositeHIDInstance->_autoSendTaskHandle);
290 |     }
291 | 
292 |     // Wait to let the server start up
293 |     vTaskDelay(portMAX_DELAY);
294 | }
295 | 


--------------------------------------------------------------------------------
/BleCompositeHID.h:
--------------------------------------------------------------------------------
 1 | #ifndef ESP32_BLE_MULTI_HID_H
 2 | #define ESP32_BLE_MULTI_HID_H
 3 | #include "sdkconfig.h"
 4 | #if defined(CONFIG_BT_ENABLED)
 5 | 
 6 | #include "nimconfig.h"
 7 | #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
 8 | 
 9 | #include "BleConnectionStatus.h"
10 | #include "NimBLEHIDDevice.h"
11 | #include "NimBLECharacteristic.h"
12 | 
13 | #include "BLEHostConfiguration.h"
14 | #include "BaseCompositeDevice.h"
15 | 
16 | #include <vector>
17 | #include "SafeQueue.hpp"
18 | 
19 | class BleCompositeHID
20 | {
21 | public:
22 |     BleCompositeHID(std::string deviceName = "ESP32 BLE Composite HID", std::string deviceManufacturer = "Espressif", uint8_t batteryLevel = 100);
23 |     ~BleCompositeHID();
24 |     void begin();
25 |     void begin(const BLEHostConfiguration& config);
26 |     void end();
27 | 
28 |     void setBatteryLevel(uint8_t level);
29 |     void addDevice(BaseCompositeDevice* device);
30 |     bool isConnected();
31 | 
32 |     void queueDeviceDeferredReport(std::function<void()> && reportFunc);
33 |     void sendDeferredReports();
34 | 
35 |     uint8_t batteryLevel;
36 |     std::string deviceManufacturer;
37 |     std::string deviceName;
38 | 
39 | protected:
40 |     virtual void onStarted(NimBLEServer *pServer){};
41 | 
42 | private:
43 |     static void taskServer(void *pvParameter);
44 |     static void timedSendDeferredReports(void *pvParameter);
45 | 
46 |     BLEHostConfiguration _configuration;
47 |     BleConnectionStatus* _connectionStatus;
48 |     NimBLEHIDDevice* _hid;
49 | 
50 |     std::vector<BaseCompositeDevice*> _devices;
51 |     SafeQueue<std::function<void()>> _deferredReports;
52 |     TaskHandle_t _autoSendTaskHandle;
53 | };
54 | 
55 | #endif // CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
56 | #endif // CONFIG_BT_ENABLED
57 | #endif // ESP32_BLE_MULTI_HID_H
58 | 


--------------------------------------------------------------------------------
/BleConnectionStatus.cpp:
--------------------------------------------------------------------------------
 1 | #include "BleConnectionStatus.h"
 2 | 
 3 | BleConnectionStatus::BleConnectionStatus(void)
 4 | {
 5 | }
 6 | 
 7 | void BleConnectionStatus::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo)
 8 | {
 9 |     pServer->updateConnParams(connInfo.getConnHandle(), 6, 7, 0, 600);
10 |     this->connected = true;
11 | }
12 | 
13 | void BleConnectionStatus::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason)
14 | {
15 |     this->connected = false;
16 | }
17 | 
18 | bool BleConnectionStatus::isConnected(){
19 |     return this->connected;
20 | }
21 | 


--------------------------------------------------------------------------------
/BleConnectionStatus.h:
--------------------------------------------------------------------------------
 1 | #ifndef ESP32_BLE_CONNECTION_STATUS_H
 2 | #define ESP32_BLE_CONNECTION_STATUS_H
 3 | #include "sdkconfig.h"
 4 | #if defined(CONFIG_BT_ENABLED)
 5 | 
 6 | #include "nimconfig.h"
 7 | #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
 8 | 
 9 | #include <NimBLEServer.h>
10 | #include "NimBLECharacteristic.h"
11 | 
12 | class BleConnectionStatus : public NimBLEServerCallbacks
13 | {
14 | public:
15 |     BleConnectionStatus(void);
16 |     void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override;
17 |     void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override;
18 |     bool isConnected();
19 | private:
20 |     bool connected = false;
21 | };
22 | 
23 | #endif // CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
24 | #endif // CONFIG_BT_ENABLED
25 | #endif // ESP32_BLE_CONNECTION_STATUS_H
26 | 


--------------------------------------------------------------------------------
/GamepadConfiguration.cpp:
--------------------------------------------------------------------------------
  1 | #include "GamepadConfiguration.h"
  2 | 
  3 | #if defined(CONFIG_ARDUHAL_ESP_LOG)
  4 | #include "esp32-hal-log.h"
  5 | #define LOG_TAG "GamepadConfiguration"
  6 | #else
  7 | #include "esp_log.h"
  8 | static const char *LOG_TAG = "GamepadConfiguration";
  9 | #endif
 10 | 
 11 | GamepadConfiguration::GamepadConfiguration() : 
 12 |     BaseCompositeDeviceConfiguration(GAMEPAD_REPORT_ID),
 13 |     _controllerType(CONTROLLER_TYPE_GAMEPAD),
 14 |     _buttonCount(16),
 15 |     _hatSwitchCount(1),
 16 |     _whichSpecialButtons{false, false, false, false, false, false, false, false},
 17 |     _whichAxes{true, true, true, true, true, true, true, true},
 18 |     _whichSimulationControls{false, false, false, false, false},
 19 |     _axesMin(0x0000),
 20 |     _axesMax(0x7FFF),
 21 |     _simulationMin(0x0000),
 22 |     _simulationMax(0x7FFF),
 23 |     _includeRumble(false),
 24 |     _includePlayerIndicators(false)
 25 | {
 26 | }
 27 | 
 28 | const char* GamepadConfiguration::getDeviceName() const 
 29 | {
 30 |     return GAMEPAD_DEVICE_NAME;
 31 | }
 32 | 
 33 | uint8_t GamepadConfiguration::getDeviceReportSize() const
 34 | {
 35 |     uint8_t numOfButtonBytes = getButtonNumBytes();
 36 |     uint8_t numOfSpecialButtonBytes = getSpecialButtonNumBytes();
 37 |     uint8_t numOfAxisBytes = this->getAxisCount() * 2;
 38 |     uint8_t numOfSimulationBytes = this->getSimulationCount() * 2;
 39 | 
 40 |     uint8_t reportSize = numOfButtonBytes + numOfSpecialButtonBytes + numOfAxisBytes + numOfSimulationBytes + this->getHatSwitchCount();
 41 |     return reportSize;
 42 | }
 43 | 
 44 | size_t GamepadConfiguration::makeDeviceReport(uint8_t* buffer, size_t bufferSize) const
 45 | {
 46 |     // Report description START -------------------------------------------------
 47 | 
 48 |     uint8_t tempHidReportDescriptor[BLE_ATT_ATTR_MAX_LEN];
 49 |     size_t reportSize = 0;
 50 | 
 51 |     // USAGE_PAGE (Generic Desktop)
 52 |     tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); //0x05;
 53 |     tempHidReportDescriptor[reportSize++] = 0x01; //Generic Desktop
 54 | 
 55 |     // USAGE (Joystick - 0x04; Gamepad - 0x05; Multi-axis Controller - 0x08)
 56 |     tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
 57 |     tempHidReportDescriptor[reportSize++] = this->getControllerType();
 58 | 
 59 |     // COLLECTION (Application)
 60 |     tempHidReportDescriptor[reportSize++] = COLLECTION(1); //0xa1;
 61 |     tempHidReportDescriptor[reportSize++] = 0x01;
 62 |     {
 63 |         // REPORT_ID (Gamepad)
 64 |         tempHidReportDescriptor[reportSize++] = REPORT_ID(1);
 65 |         tempHidReportDescriptor[reportSize++] = this->getReportId();
 66 | 
 67 |         if (this->getButtonCount() > 0)
 68 |         {
 69 |             // USAGE_PAGE (Button)
 70 |             tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); //0x05;
 71 |             tempHidReportDescriptor[reportSize++] = 0x09;
 72 | 
 73 |             // LOGICAL_MINIMUM (0)
 74 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MINIMUM(1);//0x15;
 75 |             tempHidReportDescriptor[reportSize++] = 0x00;
 76 | 
 77 |             // LOGICAL_MAXIMUM (1)
 78 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MAXIMUM(1); //0x25;
 79 |             tempHidReportDescriptor[reportSize++] = 0x01;
 80 | 
 81 |             // REPORT_SIZE (1)
 82 |             tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
 83 |             tempHidReportDescriptor[reportSize++] = 0x01;
 84 | 
 85 |             // USAGE_MINIMUM (Button 1)
 86 |             tempHidReportDescriptor[reportSize++] = USAGE_MINIMUM(1);//0x19;
 87 |             tempHidReportDescriptor[reportSize++] = 0x01;
 88 | 
 89 |             // USAGE_MAXIMUM (Up to 128 buttons possible)
 90 |             tempHidReportDescriptor[reportSize++] = USAGE_MAXIMUM(1);//0x29;
 91 |             tempHidReportDescriptor[reportSize++] = this->getButtonCount();
 92 | 
 93 |             // REPORT_COUNT (# of buttons)
 94 |             tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
 95 |             tempHidReportDescriptor[reportSize++] = this->getButtonCount();
 96 | 
 97 |             // INPUT (Data,Var,Abs)
 98 |             tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
 99 |             tempHidReportDescriptor[reportSize++] = 0x02;
100 | 
101 |             uint8_t buttonPaddingBits = getButtonNumPaddingBits();
102 |             if (buttonPaddingBits > 0)
103 |             {
104 |                 // REPORT_SIZE (1)
105 |                 tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
106 |                 tempHidReportDescriptor[reportSize++] = 0x01;
107 | 
108 |                 // REPORT_COUNT (# of padding bits)
109 |                 tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
110 |                 tempHidReportDescriptor[reportSize++] = buttonPaddingBits;
111 | 
112 |                 // INPUT (Const,Var,Abs)
113 |                 tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
114 |                 tempHidReportDescriptor[reportSize++] = 0x03;
115 | 
116 |             } // Padding Bits Needed
117 | 
118 |         } // Buttons
119 | 
120 |         if (this->getTotalSpecialButtonCount() > 0)
121 |         {
122 |             // LOGICAL_MINIMUM (0)
123 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MINIMUM(1); //0x15;
124 |             tempHidReportDescriptor[reportSize++] = 0x00;
125 | 
126 |             // LOGICAL_MAXIMUM (1)
127 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MAXIMUM(1); //;0x25;
128 |             tempHidReportDescriptor[reportSize++] = 0x01;
129 | 
130 |             // REPORT_SIZE (1)
131 |             tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
132 |             tempHidReportDescriptor[reportSize++] = 0x01;
133 | 
134 |             if (this->getDesktopSpecialButtonCount() > 0)
135 |             {
136 |                 // USAGE_PAGE (Generic Desktop)
137 |                 tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); // 0x05;
138 |                 tempHidReportDescriptor[reportSize++] = 0x01;
139 | 
140 |                 // REPORT_COUNT
141 |                 tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
142 |                 tempHidReportDescriptor[reportSize++] = this->getDesktopSpecialButtonCount();
143 |                 if (this->getIncludeStart())
144 |                 {
145 |                     // USAGE (Start)
146 |                     tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
147 |                     tempHidReportDescriptor[reportSize++] = 0x3D;
148 |                 }
149 | 
150 |                 if (this->getIncludeSelect())
151 |                 {
152 |                     // USAGE (Select)
153 |                     tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
154 |                     tempHidReportDescriptor[reportSize++] = 0x3E;
155 |                 }
156 | 
157 |                 if (this->getIncludeMenu())
158 |                 {
159 |                     // USAGE (App Menu)
160 |                     tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
161 |                     tempHidReportDescriptor[reportSize++] = 0x86;
162 |                 }
163 | 
164 |                 // INPUT (Data,Var,Abs)
165 |                 tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
166 |                 tempHidReportDescriptor[reportSize++] = 0x02;
167 |             }
168 | 
169 |             if (this->getConsumerSpecialButtonCount() > 0)
170 |             {
171 |                 // USAGE_PAGE (Consumer Page)
172 |                 tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); //0x05;
173 |                 tempHidReportDescriptor[reportSize++] = 0x0C;
174 | 
175 |                 // REPORT_COUNT
176 |                 tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
177 |                 tempHidReportDescriptor[reportSize++] = this->getConsumerSpecialButtonCount();
178 | 
179 |                 if (this->getIncludeHome())
180 |                 {
181 |                     // USAGE (Home)
182 |                     tempHidReportDescriptor[reportSize++] = USAGE(2); //0x0A;
183 |                     tempHidReportDescriptor[reportSize++] = 0x23;
184 |                     tempHidReportDescriptor[reportSize++] = 0x02;
185 |                 }
186 | 
187 |                 if (this->getIncludeBack())
188 |                 {
189 |                     // USAGE (Back)
190 |                     tempHidReportDescriptor[reportSize++] = USAGE(2); //0x0A;
191 |                     tempHidReportDescriptor[reportSize++] = 0x24;
192 |                     tempHidReportDescriptor[reportSize++] = 0x02;
193 |                 }
194 | 
195 |                 if (this->getIncludeVolumeInc())
196 |                 {
197 |                     // USAGE (Volume Increment)
198 |                     tempHidReportDescriptor[reportSize++] =  USAGE(1); //0x09;
199 |                     tempHidReportDescriptor[reportSize++] = 0xE9;
200 |                 }
201 | 
202 |                 if (this->getIncludeVolumeDec())
203 |                 {
204 |                     // USAGE (Volume Decrement)
205 |                     tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
206 |                     tempHidReportDescriptor[reportSize++] = 0xEA;
207 |                 }
208 | 
209 |                 if (this->getIncludeVolumeMute())
210 |                 {
211 |                     // USAGE (Mute)
212 |                     tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
213 |                     tempHidReportDescriptor[reportSize++] = 0xE2;
214 |                 }
215 | 
216 |                 // INPUT (Data,Var,Abs)
217 |                 tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
218 |                 tempHidReportDescriptor[reportSize++] = 0x02;
219 |             }
220 | 
221 |             uint8_t specialButtonPaddingBits = getSpecialButtonNumPaddingBits();
222 |             if (specialButtonPaddingBits > 0)
223 |             {
224 |                 // REPORT_SIZE (1)
225 |                 tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
226 |                 tempHidReportDescriptor[reportSize++] = 0x01;
227 | 
228 |                 // REPORT_COUNT (# of padding bits)
229 |                 tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
230 |                 tempHidReportDescriptor[reportSize++] = specialButtonPaddingBits;
231 | 
232 |                 // INPUT (Const,Var,Abs)
233 |                 tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
234 |                 tempHidReportDescriptor[reportSize++] = 0x03;
235 | 
236 |             } // Padding Bits Needed
237 | 
238 |         } // Special Buttons
239 | 
240 |         if (this->getAxisCount() > 0)
241 |         {
242 |             // USAGE_PAGE (Generic Desktop)
243 |             tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); 0x05;
244 |             tempHidReportDescriptor[reportSize++] = 0x01; // Generic desktop controls
245 | 
246 |             // USAGE (Pointer)
247 |             tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
248 |             tempHidReportDescriptor[reportSize++] = 0x01; 
249 | 
250 |             // LOGICAL_MINIMUM (-32767)
251 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MINIMUM(2); //0x16;
252 |             tempHidReportDescriptor[reportSize++] = lowByte(this->getAxesMin());
253 |             tempHidReportDescriptor[reportSize++] = highByte(this->getAxesMin());
254 |             //tempHidReportDescriptor[reportSize++] = 0x00;		// Use these two lines for 0 min
255 |             //tempHidReportDescriptor[reportSize++] = 0x00;
256 |                 //tempHidReportDescriptor[reportSize++] = 0x01;	// Use these two lines for -32767 min
257 |             //tempHidReportDescriptor[reportSize++] = 0x80;
258 | 
259 |             // LOGICAL_MAXIMUM (+32767)
260 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MAXIMUM(2);//0x26;
261 |             tempHidReportDescriptor[reportSize++] = lowByte(this->getAxesMax());
262 |             tempHidReportDescriptor[reportSize++] = highByte(this->getAxesMax());
263 |             //tempHidReportDescriptor[reportSize++] = 0xFF;	// Use these two lines for 255 max
264 |             //tempHidReportDescriptor[reportSize++] = 0x00;
265 |                 //tempHidReportDescriptor[reportSize++] = 0xFF;	// Use these two lines for +32767 max
266 |             //tempHidReportDescriptor[reportSize++] = 0x7F;
267 | 
268 |             // REPORT_SIZE (16)
269 |             tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
270 |             tempHidReportDescriptor[reportSize++] = 0x10;
271 | 
272 |             // REPORT_COUNT (this->getAxisCount())
273 |             tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
274 |             tempHidReportDescriptor[reportSize++] = this->getAxisCount();
275 | 
276 |             // COLLECTION (Physical)
277 |             tempHidReportDescriptor[reportSize++] = COLLECTION(1); //0xA1;
278 |             tempHidReportDescriptor[reportSize++] = 0x00;
279 | 
280 |             if (this->getIncludeXAxis())
281 |             {
282 |                 // USAGE (X)
283 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
284 |                 tempHidReportDescriptor[reportSize++] = 0x30;
285 |             }
286 | 
287 |             if (this->getIncludeYAxis())
288 |             {
289 |                 // USAGE (Y)
290 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
291 |                 tempHidReportDescriptor[reportSize++] = 0x31;
292 |             }
293 | 
294 |             if (this->getIncludeZAxis())
295 |             {
296 |                 // USAGE (Z)
297 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
298 |                 tempHidReportDescriptor[reportSize++] = 0x32;
299 |             }
300 | 
301 |             if (this->getIncludeRzAxis())
302 |             {
303 |                 // USAGE (Rz)
304 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
305 |                 tempHidReportDescriptor[reportSize++] = 0x35;
306 |             }
307 | 
308 |             if (this->getIncludeRxAxis())
309 |             {
310 |                 // USAGE (Rx)
311 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
312 |                 tempHidReportDescriptor[reportSize++] = 0x33;
313 |             }
314 | 
315 |             if (this->getIncludeRyAxis())
316 |             {
317 |                 // USAGE (Ry)
318 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
319 |                 tempHidReportDescriptor[reportSize++] = 0x34;
320 |             }
321 | 
322 |             if (this->getIncludeSlider1())
323 |             {
324 |                 // USAGE (Slider)
325 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
326 |                 tempHidReportDescriptor[reportSize++] = 0x36;
327 |             }
328 | 
329 |             if (this->getIncludeSlider2())
330 |             {
331 |                 // USAGE (Slider)
332 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
333 |                 tempHidReportDescriptor[reportSize++] = 0x36;
334 |             }
335 | 
336 |             // INPUT (Data,Var,Abs)
337 |             tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
338 |             tempHidReportDescriptor[reportSize++] = 0x02;
339 | 
340 |             // END_COLLECTION (Physical)
341 |             tempHidReportDescriptor[reportSize++] = END_COLLECTION(0); //0xc0;
342 | 
343 |         } // X, Y, Z, Rx, Ry, and Rz Axis
344 | 
345 |         if (this->getSimulationCount() > 0)
346 |         {
347 |             // USAGE_PAGE (Simulation Controls)
348 |             tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); //0x05;
349 |             tempHidReportDescriptor[reportSize++] = 0x02;
350 | 
351 |             // LOGICAL_MINIMUM (-32767)
352 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MINIMUM(2); //0x16;
353 |             tempHidReportDescriptor[reportSize++] = lowByte(this->getSimulationMin());
354 |             tempHidReportDescriptor[reportSize++] = highByte(this->getSimulationMin());
355 |             //tempHidReportDescriptor[reportSize++] = 0x00;		// Use these two lines for 0 min
356 |             //tempHidReportDescriptor[reportSize++] = 0x00;
357 |             //tempHidReportDescriptor[reportSize++] = 0x01;	    // Use these two lines for -32767 min
358 |             //tempHidReportDescriptor[reportSize++] = 0x80;
359 | 
360 |             // LOGICAL_MAXIMUM (+32767)
361 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MAXIMUM(2); //0x26;
362 |             tempHidReportDescriptor[reportSize++] = lowByte(this->getSimulationMax());
363 |             tempHidReportDescriptor[reportSize++] = highByte(this->getSimulationMax());
364 |             //tempHidReportDescriptor[reportSize++] = 0xFF;	    // Use these two lines for 255 max
365 |             //tempHidReportDescriptor[reportSize++] = 0x00;
366 |             //tempHidReportDescriptor[reportSize++] = 0xFF;		// Use these two lines for +32767 max
367 |             //tempHidReportDescriptor[reportSize++] = 0x7F;
368 | 
369 |             // REPORT_SIZE (16)
370 |             tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
371 |             tempHidReportDescriptor[reportSize++] = 0x10;
372 | 
373 |             // REPORT_COUNT (this->getSimulationCount())
374 |             tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
375 |             tempHidReportDescriptor[reportSize++] = this->getSimulationCount();
376 | 
377 |             // COLLECTION (Physical)
378 |             tempHidReportDescriptor[reportSize++] = COLLECTION(1); //0xA1;
379 |             tempHidReportDescriptor[reportSize++] = 0x00;
380 | 
381 |             if (this->getIncludeRudder())
382 |             {
383 |                 // USAGE (Rudder)
384 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
385 |                 tempHidReportDescriptor[reportSize++] = 0xBA;
386 |             }
387 | 
388 |             if (this->getIncludeThrottle())
389 |             {
390 |                 // USAGE (Throttle)
391 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
392 |                 tempHidReportDescriptor[reportSize++] = 0xBB;
393 |             }
394 | 
395 |             if (this->getIncludeAccelerator())
396 |             {
397 |                 // USAGE (Accelerator)
398 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
399 |                 tempHidReportDescriptor[reportSize++] = 0xC4;
400 |             }
401 | 
402 |             if (this->getIncludeBrake())
403 |             {
404 |                 // USAGE (Brake)
405 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
406 |                 tempHidReportDescriptor[reportSize++] = 0xC5;
407 |             }
408 | 
409 |             if (this->getIncludeSteering())
410 |             {
411 |                 // USAGE (Steering)
412 |                 tempHidReportDescriptor[reportSize++] = USAGE(1); //0x09;
413 |                 tempHidReportDescriptor[reportSize++] = 0xC8;
414 |             }
415 | 
416 |             // INPUT (Data,Var,Abs)
417 |             tempHidReportDescriptor[reportSize++] = HIDINPUT(1); 0x81;
418 |             tempHidReportDescriptor[reportSize++] = 0x02;
419 | 
420 |             // END_COLLECTION (Physical)
421 |             tempHidReportDescriptor[reportSize++] = END_COLLECTION(0); //0xc0;
422 | 
423 |         } // Simulation Controls
424 | 
425 |         if (this->getHatSwitchCount() > 0)
426 |         {
427 | 
428 |             // COLLECTION (Physical)
429 |             tempHidReportDescriptor[reportSize++] = COLLECTION(1); //0xA1;
430 |             tempHidReportDescriptor[reportSize++] = 0x00;
431 | 
432 |             // USAGE_PAGE (Generic Desktop)
433 |             tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1);
434 |             tempHidReportDescriptor[reportSize++] = 0x01;
435 | 
436 |             // USAGE (Hat Switch)
437 |             for (int currentHatIndex = 0; currentHatIndex < this->getHatSwitchCount(); currentHatIndex++)
438 |             {
439 |                 tempHidReportDescriptor[reportSize++] = USAGE(1);
440 |                 tempHidReportDescriptor[reportSize++] = 0x39;
441 |             }
442 | 
443 |             // Logical Min (1)
444 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MINIMUM(1); //0x15;
445 |             tempHidReportDescriptor[reportSize++] = 0x01;
446 | 
447 |             // Logical Max (8)
448 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MAXIMUM(1); //0x25;
449 |             tempHidReportDescriptor[reportSize++] = 0x08;
450 | 
451 |             // Physical Min (0)
452 |             tempHidReportDescriptor[reportSize++] = PHYSICAL_MINIMUM(1); //0x35;
453 |             tempHidReportDescriptor[reportSize++] = 0x00;
454 | 
455 |             // Physical Max (315)
456 |             tempHidReportDescriptor[reportSize++] = PHYSICAL_MAXIMUM(2); //0x46;
457 |             tempHidReportDescriptor[reportSize++] = 0x3B;
458 |             tempHidReportDescriptor[reportSize++] = 0x01;
459 | 
460 |             // Unit (SI Rot : Ang Pos)
461 |             tempHidReportDescriptor[reportSize++] = UNIT(1); //0x65;
462 |             tempHidReportDescriptor[reportSize++] = 0x12;
463 | 
464 |             // Report Size (8)
465 |             tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1); //0x75;
466 |             tempHidReportDescriptor[reportSize++] = 0x08;
467 | 
468 |             // Report Count (4)
469 |             tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); //0x95;
470 |             tempHidReportDescriptor[reportSize++] = this->getHatSwitchCount();
471 | 
472 |             // Input (Data, Variable, Absolute)
473 |             tempHidReportDescriptor[reportSize++] = HIDINPUT(1); //0x81;
474 |             tempHidReportDescriptor[reportSize++] = 0x42;
475 | 
476 |             // END_COLLECTION (Physical)
477 |             tempHidReportDescriptor[reportSize++] = END_COLLECTION(0); //0xc0;
478 |         }
479 | 
480 |         if(this->getIncludeRumble()){
481 |             ESP_LOGI(LOG_TAG, "Start offset: %d, Bytes to copy: %d, Final size: %d", reportSize, sizeof(pidReportDescriptor), reportSize + sizeof(pidReportDescriptor));
482 |             memcpy(&tempHidReportDescriptor[reportSize], &pidReportDescriptor[0], sizeof(pidReportDescriptor));
483 |             reportSize += sizeof(pidReportDescriptor) / sizeof(pidReportDescriptor[0]);
484 |         }
485 | 
486 |         if(this->getIncludePlayerIndicators()){
487 |             tempHidReportDescriptor[reportSize++] = COLLECTION(1); // Physical collection
488 |             tempHidReportDescriptor[reportSize++] = 0x00;
489 | 
490 |             tempHidReportDescriptor[reportSize++] = REPORT_ID(1); // Report ID
491 |             tempHidReportDescriptor[reportSize++] = this->getReportId();
492 | 
493 |             tempHidReportDescriptor[reportSize++] = USAGE_PAGE(1); // Usage page - LED usage page
494 |             tempHidReportDescriptor[reportSize++] = 0x08;
495 | 
496 |             tempHidReportDescriptor[reportSize++] = USAGE_MINIMUM(1); // Usage minimum
497 |             tempHidReportDescriptor[reportSize++] = 0x61;
498 | 
499 |             tempHidReportDescriptor[reportSize++] = USAGE_MAXIMUM(1); // Usage maximum
500 |             tempHidReportDescriptor[reportSize++] = 0x68;
501 | 
502 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MINIMUM(1); // Logical minimum
503 |             tempHidReportDescriptor[reportSize++] = 0x00;
504 | 
505 |             tempHidReportDescriptor[reportSize++] = LOGICAL_MAXIMUM(1); // Logical maximum
506 |             tempHidReportDescriptor[reportSize++] = 0x01;
507 | 
508 |             tempHidReportDescriptor[reportSize++] = REPORT_COUNT(1); // Report count - 8 bits
509 |             tempHidReportDescriptor[reportSize++] = 0x08;
510 | 
511 |             tempHidReportDescriptor[reportSize++] = REPORT_SIZE(1);  // Report size
512 |             tempHidReportDescriptor[reportSize++] = 0x01;
513 | 
514 |             tempHidReportDescriptor[reportSize++] = HIDOUTPUT(1); // Output
515 |             tempHidReportDescriptor[reportSize++] = 0x02;
516 | 
517 |             tempHidReportDescriptor[reportSize++] = END_COLLECTION(0); // End physical collection
518 |         }
519 |     }
520 |     
521 |     // End gamepad collection
522 |     tempHidReportDescriptor[reportSize++] = END_COLLECTION(0); //0xc0;
523 |     
524 |     if(reportSize < bufferSize){
525 |         memcpy(buffer, tempHidReportDescriptor, reportSize);
526 |     } else {
527 |         return -1;
528 |     }
529 | 
530 |     return reportSize;
531 | }
532 | 
533 | 
534 | uint8_t GamepadConfiguration::getTotalSpecialButtonCount() const
535 | {
536 |     int count = 0;
537 |     for (int i = 0; i < POSSIBLESPECIALBUTTONS; i++)
538 |     {
539 |         count += (int)_whichSpecialButtons[i];
540 |     }
541 | 
542 |     return count;
543 | }
544 | 
545 | uint8_t GamepadConfiguration::getDesktopSpecialButtonCount() const
546 | {
547 |     int count = 0;
548 |     for (int i = 0; i < 3; i++)
549 |     {
550 |         count += (int)_whichSpecialButtons[i];
551 |     }
552 | 
553 |     return count;
554 | }
555 | 
556 | uint8_t GamepadConfiguration::getConsumerSpecialButtonCount() const
557 | {
558 |     int count = 0;
559 |     for (int i = 3; i < 8; i++)
560 |     {
561 |         count += (int)_whichSpecialButtons[i];
562 |     }
563 | 
564 |     return count;
565 | }
566 | 
567 | uint8_t GamepadConfiguration::getAxisCount() const
568 | {
569 |     int count = 0;
570 |     for (int i = 0; i < POSSIBLEAXES; i++)
571 |     {
572 |         count += (int)_whichAxes[i];
573 |     }
574 | 
575 |     return count;
576 | }
577 | 
578 | uint8_t GamepadConfiguration::getSimulationCount() const
579 | {
580 |     int count = 0;
581 |     for (int i = 0; i < POSSIBLESIMULATIONCONTROLS; i++)
582 |     {
583 |         count += (int)_whichSimulationControls[i];
584 |     }
585 | 
586 |     return count;
587 | }
588 | 
589 | uint16_t GamepadConfiguration::getButtonCount() const { return _buttonCount; }
590 | uint8_t GamepadConfiguration::getHatSwitchCount() const { return _hatSwitchCount; }
591 | int16_t GamepadConfiguration::getAxesMin() const { return _axesMin; }
592 | int16_t GamepadConfiguration::getAxesMax() const { return _axesMax; }
593 | int16_t GamepadConfiguration::getSimulationMin() const { return _simulationMin; }
594 | int16_t GamepadConfiguration::getSimulationMax() const { return _simulationMax; }
595 | uint8_t GamepadConfiguration::getControllerType() const { return _controllerType; }
596 | bool GamepadConfiguration::getIncludeStart() const { return _whichSpecialButtons[START_BUTTON]; }
597 | bool GamepadConfiguration::getIncludeSelect() const { return _whichSpecialButtons[SELECT_BUTTON]; }
598 | bool GamepadConfiguration::getIncludeMenu() const { return _whichSpecialButtons[MENU_BUTTON]; }
599 | bool GamepadConfiguration::getIncludeHome() const { return _whichSpecialButtons[HOME_BUTTON]; }
600 | bool GamepadConfiguration::getIncludeBack() const { return _whichSpecialButtons[BACK_BUTTON]; }
601 | bool GamepadConfiguration::getIncludeVolumeInc() const { return _whichSpecialButtons[VOLUME_INC_BUTTON]; }
602 | bool GamepadConfiguration::getIncludeVolumeDec() const { return _whichSpecialButtons[VOLUME_DEC_BUTTON]; }
603 | bool GamepadConfiguration::getIncludeVolumeMute() const { return _whichSpecialButtons[VOLUME_MUTE_BUTTON]; }
604 | const bool *GamepadConfiguration::getWhichSpecialButtons() const { return _whichSpecialButtons; }
605 | bool GamepadConfiguration::getIncludeXAxis() const { return _whichAxes[X_AXIS]; }
606 | bool GamepadConfiguration::getIncludeYAxis() const { return _whichAxes[Y_AXIS]; }
607 | bool GamepadConfiguration::getIncludeZAxis() const { return _whichAxes[Z_AXIS]; }
608 | bool GamepadConfiguration::getIncludeRxAxis() const { return _whichAxes[RX_AXIS]; }
609 | bool GamepadConfiguration::getIncludeRyAxis() const { return _whichAxes[RY_AXIS]; }
610 | bool GamepadConfiguration::getIncludeRzAxis() const { return _whichAxes[RZ_AXIS]; }
611 | bool GamepadConfiguration::getIncludeSlider1() const { return _whichAxes[SLIDER1]; }
612 | bool GamepadConfiguration::getIncludeSlider2() const { return _whichAxes[SLIDER2]; }
613 | const bool *GamepadConfiguration::getWhichAxes() const { return _whichAxes; }
614 | bool GamepadConfiguration::getIncludeRudder() const { return _whichSimulationControls[RUDDER]; }
615 | bool GamepadConfiguration::getIncludeThrottle() const { return _whichSimulationControls[THROTTLE]; }
616 | bool GamepadConfiguration::getIncludeAccelerator() const { return _whichSimulationControls[ACCELERATOR]; }
617 | bool GamepadConfiguration::getIncludeBrake() const { return _whichSimulationControls[BRAKE]; }
618 | bool GamepadConfiguration::getIncludeSteering() const { return _whichSimulationControls[STEERING]; }
619 | const bool *GamepadConfiguration::getWhichSimulationControls() const { return _whichSimulationControls; }
620 | bool GamepadConfiguration::getIncludeRumble() const { return _includeRumble; }
621 | bool GamepadConfiguration::getIncludePlayerIndicators() const { return _includePlayerIndicators; }
622 | 
623 | void GamepadConfiguration::setIncludeRumble(bool value) { _includeRumble = value; }
624 | void GamepadConfiguration::setIncludePlayerIndicators(bool value) { _includePlayerIndicators = value; }
625 | 
626 | void GamepadConfiguration::setWhichSpecialButtons(bool start, bool select, bool menu, bool home, bool back, bool volumeInc, bool volumeDec, bool volumeMute)
627 | {
628 |     _whichSpecialButtons[START_BUTTON] = start;
629 |     _whichSpecialButtons[SELECT_BUTTON] = select;
630 |     _whichSpecialButtons[MENU_BUTTON] = menu;
631 |     _whichSpecialButtons[HOME_BUTTON] = home;
632 |     _whichSpecialButtons[BACK_BUTTON] = back;
633 |     _whichSpecialButtons[VOLUME_INC_BUTTON] = volumeInc;
634 |     _whichSpecialButtons[VOLUME_DEC_BUTTON] = volumeDec;
635 |     _whichSpecialButtons[VOLUME_MUTE_BUTTON] = volumeMute;
636 | }
637 | 
638 | void GamepadConfiguration::setWhichAxes(bool xAxis, bool yAxis, bool zAxis, bool rxAxis, bool ryAxis, bool rzAxis, bool slider1, bool slider2)
639 | {
640 |     _whichAxes[X_AXIS] = xAxis;
641 |     _whichAxes[Y_AXIS] = yAxis;
642 |     _whichAxes[Z_AXIS] = zAxis;
643 |     _whichAxes[RX_AXIS] = rxAxis;
644 |     _whichAxes[RY_AXIS] = ryAxis;
645 |     _whichAxes[RZ_AXIS] = rzAxis;
646 |     _whichAxes[SLIDER1] = slider1;
647 |     _whichAxes[SLIDER2] = slider2;
648 | }
649 | 
650 | void GamepadConfiguration::setWhichSimulationControls(bool rudder, bool throttle, bool accelerator, bool brake, bool steering)
651 | {
652 |     _whichSimulationControls[RUDDER] = rudder;
653 |     _whichSimulationControls[THROTTLE] = throttle;
654 |     _whichSimulationControls[ACCELERATOR] = accelerator;
655 |     _whichSimulationControls[BRAKE] = brake;
656 |     _whichSimulationControls[STEERING] = steering;
657 | }
658 | 
659 | void GamepadConfiguration::setControllerType(uint8_t value) { _controllerType = value; }
660 | 
661 | void GamepadConfiguration::setButtonCount(uint16_t value) { _buttonCount = value; }
662 | void GamepadConfiguration::setHatSwitchCount(uint8_t value) { _hatSwitchCount = value; }
663 | void GamepadConfiguration::setIncludeStart(bool value) { _whichSpecialButtons[START_BUTTON] = value; }
664 | void GamepadConfiguration::setIncludeSelect(bool value) { _whichSpecialButtons[SELECT_BUTTON] = value; }
665 | void GamepadConfiguration::setIncludeMenu(bool value) { _whichSpecialButtons[MENU_BUTTON] = value; }
666 | void GamepadConfiguration::setIncludeHome(bool value) { _whichSpecialButtons[HOME_BUTTON] = value; }
667 | void GamepadConfiguration::setIncludeBack(bool value) { _whichSpecialButtons[BACK_BUTTON] = value; }
668 | void GamepadConfiguration::setIncludeVolumeInc(bool value) { _whichSpecialButtons[VOLUME_INC_BUTTON] = value; }
669 | void GamepadConfiguration::setIncludeVolumeDec(bool value) { _whichSpecialButtons[VOLUME_DEC_BUTTON] = value; }
670 | void GamepadConfiguration::setIncludeVolumeMute(bool value) { _whichSpecialButtons[VOLUME_MUTE_BUTTON] = value; }
671 | void GamepadConfiguration::setIncludeXAxis(bool value) { _whichAxes[X_AXIS] = value; }
672 | void GamepadConfiguration::setIncludeYAxis(bool value) { _whichAxes[Y_AXIS] = value; }
673 | void GamepadConfiguration::setIncludeZAxis(bool value) { _whichAxes[Z_AXIS] = value; }
674 | void GamepadConfiguration::setIncludeRxAxis(bool value) { _whichAxes[RX_AXIS] = value; }
675 | void GamepadConfiguration::setIncludeRyAxis(bool value) { _whichAxes[RY_AXIS] = value; }
676 | void GamepadConfiguration::setIncludeRzAxis(bool value) { _whichAxes[RZ_AXIS] = value; }
677 | void GamepadConfiguration::setIncludeSlider1(bool value) { _whichAxes[SLIDER1] = value; }
678 | void GamepadConfiguration::setIncludeSlider2(bool value) { _whichAxes[SLIDER2] = value; }
679 | void GamepadConfiguration::setIncludeRudder(bool value) { _whichSimulationControls[RUDDER] = value; }
680 | void GamepadConfiguration::setIncludeThrottle(bool value) { _whichSimulationControls[THROTTLE] = value; }
681 | void GamepadConfiguration::setIncludeAccelerator(bool value) { _whichSimulationControls[ACCELERATOR] = value; }
682 | void GamepadConfiguration::setIncludeBrake(bool value) { _whichSimulationControls[BRAKE] = value; }
683 | void GamepadConfiguration::setIncludeSteering(bool value) { _whichSimulationControls[STEERING] = value; }
684 | void GamepadConfiguration::setAxesMin(int16_t value) { _axesMin = value; }
685 | void GamepadConfiguration::setAxesMax(int16_t value) { _axesMax = value; }
686 | void GamepadConfiguration::setSimulationMin(int16_t value) { _simulationMin = value; }
687 | void GamepadConfiguration::setSimulationMax(int16_t value) { _simulationMax = value; }
688 | 
689 | 
690 | uint8_t GamepadConfiguration::getButtonNumBytes() const
691 | {
692 |     uint8_t buttonNumBytes = this->getButtonCount() / 8;
693 |     if (getButtonNumPaddingBits() > 0){
694 |         buttonNumBytes++;
695 |     }
696 | 
697 |     return buttonNumBytes;
698 | }
699 | 
700 | uint8_t GamepadConfiguration::getButtonNumPaddingBits() const
701 | {
702 |     uint8_t buttonPaddingBits = 8 - (this->getButtonCount() % 8);
703 |     if (buttonPaddingBits == 8)
704 |     {
705 |         buttonPaddingBits = 0;
706 |     }
707 |     return buttonPaddingBits;
708 | }
709 | 
710 | uint8_t GamepadConfiguration::getSpecialButtonNumBytes() const
711 | {
712 |     uint8_t specialButtonNumBytes = this->getTotalSpecialButtonCount() / 8;
713 |     if (getSpecialButtonNumPaddingBits() > 0){
714 |         specialButtonNumBytes++;
715 |     }
716 | 
717 |     return specialButtonNumBytes;
718 | }
719 | 
720 | uint8_t GamepadConfiguration::getSpecialButtonNumPaddingBits() const
721 | {
722 |     uint8_t specialButtonPaddingBits = 8 - (this->getTotalSpecialButtonCount() % 8);
723 |     if (specialButtonPaddingBits == 8)
724 |     {
725 |         specialButtonPaddingBits = 0;
726 |     }
727 |     return specialButtonPaddingBits;
728 | }
729 | 


--------------------------------------------------------------------------------
/GamepadConfiguration.h:
--------------------------------------------------------------------------------
/GamepadDevice.cpp:
--------------------------------------------------------------------------------
   1 | #include "GamepadDevice.h"
   2 | #include "BleCompositeHID.h"
   3 | 
   4 | #if defined(CONFIG_ARDUHAL_ESP_LOG)
   5 | #include "esp32-hal-log.h"
   6 | #define LOG_TAG "GamepadDevice"
   7 | #else
   8 | #include "esp_log.h"
   9 | static const char *LOG_TAG = "GamepadDevice";
  10 | #endif
  11 | 
  12 | GamepadCallbacks::GamepadCallbacks(GamepadDevice* device) : _device(device)
  13 | {
  14 | }
  15 | 
  16 | void GamepadCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo)
  17 | {
  18 |     ESP_LOGD(LOG_TAG, "GamepadCallbacks::onWrite, value: %s", pCharacteristic->getValue().c_str());
  19 |     
  20 |     uint8_t playerIndicatorBitflags = pCharacteristic->getValue<uint8_t>();
  21 |     uint8_t playerIndicator = 0;
  22 |     
  23 |     // Iterate over each bit position
  24 |     for (int i = 0; i < 8; ++i) {
  25 |         // Check if the bit is set (1)
  26 |         if ((playerIndicatorBitflags & (1 << i)) != 0) {
  27 |             // If the bit is set, add the corresponding decimal value
  28 |             playerIndicator += (1 << i);
  29 |         }
  30 |     }
  31 | 
  32 |     // Ensure the result is in the range 1 to 8
  33 |     playerIndicator = (playerIndicator % 8) + 1;
  34 | 
  35 |     _device->playerIndicator = playerIndicator;
  36 |     _device->onPlayerIndicatorChanged.fire(playerIndicator);
  37 | }
  38 | 
  39 | void GamepadCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo)
  40 | {
  41 |     ESP_LOGD(LOG_TAG, "GamepadCallbacks::onRead");
  42 | }
  43 | 
  44 | void GamepadCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code)
  45 | {
  46 |     ESP_LOGD(LOG_TAG, "GamepadCallbacks::onStatus, code: %d", code);
  47 | }
  48 | 
  49 | void GamepadCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue)
  50 | {
  51 |     ESP_LOGD(LOG_TAG, "GamepadCallbacks::onSubscribe");
  52 | }
  53 | 
  54 | GamepadDevice::GamepadDevice() : 
  55 |     _config(GamepadConfiguration()), 
  56 |     _buttons(),
  57 |     _specialButtons(),
  58 |     _x(0),
  59 |     _y(0),
  60 |     _z(0),
  61 |     _rZ(0),
  62 |     _rX(0),
  63 |     _rY(0),
  64 |     _slider1(0),
  65 |     _slider2(0),
  66 |     _rudder(0),
  67 |     _throttle(0),
  68 |     _accelerator(0),
  69 |     _brake(0),
  70 |     _steering(0),
  71 |     _hat1(0),
  72 |     _hat2(0),
  73 |     _hat3(0),
  74 |     _hat4(0),
  75 |     _callbacks(nullptr)
  76 |     // _setEffectCharacteristic(nullptr),
  77 |     // _setEnvelopeCharacteristic(nullptr),
  78 |     // _setConditionCharacteristic(nullptr),
  79 |     // _setPeriodicCharacteristic(nullptr),
  80 |     // _setConstantCharacteristic(nullptr),
  81 |     // _setRampCharacteristic(nullptr),
  82 |     // _setCustomForceCharacteristic(nullptr),
  83 |     // _downloadForceCharacteristic(nullptr),
  84 |     // _effectOperationCharacteristic(nullptr),
  85 |     // _pidDeviceControlCharacteristic(nullptr),
  86 |     // _deviceGainCharacteristic(nullptr),
  87 |     // _pidState(nullptr),
  88 |     // _createNewEffect(nullptr),
  89 |     // _pidBlockLoad(nullptr),
  90 |     // _pidPool(nullptr)
  91 | {
  92 |     this->resetButtons();
  93 | }
  94 | 
  95 | GamepadDevice::GamepadDevice(const GamepadConfiguration& config):
  96 |     _config(config), 
  97 |     _buttons(),
  98 |     _specialButtons(),
  99 |     _x(0),
 100 |     _y(0),
 101 |     _z(0),
 102 |     _rZ(0),
 103 |     _rX(0),
 104 |     _rY(0),
 105 |     _slider1(0),
 106 |     _slider2(0),
 107 |     _rudder(0),
 108 |     _throttle(0),
 109 |     _accelerator(0),
 110 |     _brake(0),
 111 |     _steering(0),
 112 |     _hat1(0),
 113 |     _hat2(0),
 114 |     _hat3(0),
 115 |     _hat4(0),
 116 |     _callbacks(nullptr)
 117 |     // _setEffectCharacteristic(nullptr),
 118 |     // _setEnvelopeCharacteristic(nullptr),
 119 |     // _setConditionCharacteristic(nullptr),
 120 |     // _setPeriodicCharacteristic(nullptr),
 121 |     // _setConstantCharacteristic(nullptr),
 122 |     // _setRampCharacteristic(nullptr),
 123 |     // _setCustomForceCharacteristic(nullptr),
 124 |     // _downloadForceCharacteristic(nullptr),
 125 |     // _effectOperationCharacteristic(nullptr),
 126 |     // _pidDeviceControlCharacteristic(nullptr),
 127 |     // _deviceGainCharacteristic(nullptr),
 128 |     // _pidState(nullptr),
 129 |     // _createNewEffect(nullptr),
 130 |     // _pidBlockLoad(nullptr),
 131 |     // _pidPool(nullptr)
 132 | {
 133 |     this->resetButtons();
 134 | }
 135 | 
 136 | GamepadDevice::~GamepadDevice()
 137 | {
 138 |     if (getOutput() && _callbacks){
 139 |         getOutput()->setCallbacks(nullptr);
 140 |         delete _callbacks;
 141 |     }
 142 | }
 143 | 
 144 | void GamepadDevice::init(NimBLEHIDDevice* hid)
 145 | {
 146 |     // Create input characteristic to send events to the computer
 147 |     auto input = hid->inputReport(_config.getReportId());
 148 | 
 149 |     // Create output characteristic to handle events coming from the computer
 150 |     auto output = hid->outputReport(_config.getReportId());
 151 | 
 152 |     // Set callbacks
 153 |     _callbacks = new GamepadCallbacks(this);
 154 |     output->setCallbacks(_callbacks);
 155 | 
 156 |     setCharacteristics(input, output);
 157 | }
 158 | 
 159 | const BaseCompositeDeviceConfiguration* GamepadDevice::getDeviceConfig() const
 160 | {
 161 |     return &_config;
 162 | }
 163 | 
 164 | void GamepadDevice::resetButtons()
 165 | {
 166 |     std::lock_guard<std::mutex> lock(_mutex);
 167 |     memset(&_buttons, 0, sizeof(_buttons));
 168 | }
 169 | 
 170 | void GamepadDevice::setAxes(int16_t x, int16_t y, int16_t z, int16_t rZ, int16_t rX, int16_t rY, int16_t slider1, int16_t slider2)
 171 | {
 172 |     if (x == -32768)
 173 |     {
 174 |         x = -32767;
 175 |     }
 176 |     if (y == -32768)
 177 |     {
 178 |         y = -32767;
 179 |     }
 180 |     if (z == -32768)
 181 |     {
 182 |         z = -32767;
 183 |     }
 184 |     if (rZ == -32768)
 185 |     {
 186 |         rZ = -32767;
 187 |     }
 188 |     if (rX == -32768)
 189 |     {
 190 |         rX = -32767;
 191 |     }
 192 |     if (rY == -32768)
 193 |     {
 194 |         rY = -32767;
 195 |     }
 196 |     if (slider1 == -32768)
 197 |     {
 198 |         slider1 = -32767;
 199 |     }
 200 |     if (slider2 == -32768)
 201 |     {
 202 |         slider2 = -32767;
 203 |     }
 204 | 
 205 |     {
 206 |         std::lock_guard<std::mutex> lock(_mutex);
 207 |         _x = x;
 208 |         _y = y;
 209 |         _z = z;
 210 |         _rZ = rZ;
 211 |         _rX = rX;
 212 |         _rY = rY;
 213 |         _slider1 = slider1;
 214 |         _slider2 = slider2;
 215 |     }
 216 | 
 217 |     if (_config.getAutoReport())
 218 |     {
 219 |         sendGamepadReport();
 220 |     }
 221 | }
 222 | 
 223 | void GamepadDevice::setSimulationControls(int16_t rudder, int16_t throttle, int16_t accelerator, int16_t brake, int16_t steering)
 224 | {
 225 |     if (rudder == -32768)
 226 |     {
 227 |         rudder = -32767;
 228 |     }
 229 |     if (throttle == -32768)
 230 |     {
 231 |         throttle = -32767;
 232 |     }
 233 |     if (accelerator == -32768)
 234 |     {
 235 |         accelerator = -32767;
 236 |     }
 237 |     if (brake == -32768)
 238 |     {
 239 |         brake = -32767;
 240 |     }
 241 |     if (steering == -32768)
 242 |     {
 243 |         steering = -32767;
 244 |     }
 245 | 
 246 |     {
 247 |         std::lock_guard<std::mutex> lock(_mutex);
 248 |         _rudder = rudder;
 249 |         _throttle = throttle;
 250 |         _accelerator = accelerator;
 251 |         _brake = brake;
 252 |         _steering = steering;
 253 |     }
 254 | 
 255 |     if (_config.getAutoReport())
 256 |     {
 257 |         sendGamepadReport();
 258 |     }
 259 | }
 260 | 
 261 | void GamepadDevice::setHats(signed char hat1, signed char hat2, signed char hat3, signed char hat4)
 262 | {
 263 |     {
 264 |         std::lock_guard<std::mutex> lock(_mutex);
 265 |         _hat1 = hat1;
 266 |         _hat2 = hat2;
 267 |         _hat3 = hat3;
 268 |         _hat4 = hat4;
 269 |     }
 270 | 
 271 |     if (_config.getAutoReport())
 272 |     {
 273 |         sendGamepadReport();
 274 |     }
 275 | }
 276 | 
 277 | void GamepadDevice::setSliders(int16_t slider1, int16_t slider2)
 278 | {
 279 |     if (slider1 == -32768)
 280 |     {
 281 |         slider1 = -32767;
 282 |     }
 283 |     if (slider2 == -32768)
 284 |     {
 285 |         slider2 = -32767;
 286 |     }
 287 | 
 288 |     {
 289 |         std::lock_guard<std::mutex> lock(_mutex);
 290 |         _slider1 = slider1;
 291 |         _slider2 = slider2;
 292 |     }
 293 | 
 294 |     if (_config.getAutoReport())
 295 |     {
 296 |         sendGamepadReport();
 297 |     }
 298 | }
 299 | 
 300 | void GamepadDevice::press(uint8_t b)
 301 | {
 302 |     uint8_t index = (b - 1) / 8;
 303 |     uint8_t bit = (b - 1) % 8;
 304 |     uint8_t bitmask = (1 << bit);
 305 | 
 306 |     uint8_t result = _buttons[index] | bitmask;
 307 | 
 308 |     if (result != _buttons[index])
 309 |     {
 310 |         std::lock_guard<std::mutex> lock(_mutex);
 311 |         _buttons[index] = result;
 312 |     }
 313 | 
 314 |     if (_config.getAutoReport())
 315 |     {
 316 |         sendGamepadReport();
 317 |     }
 318 | }
 319 | 
 320 | void GamepadDevice::release(uint8_t b)
 321 | {
 322 |     uint8_t index = (b - 1) / 8;
 323 |     uint8_t bit = (b - 1) % 8;
 324 |     uint8_t bitmask = (1 << bit);
 325 | 
 326 |     uint64_t result = _buttons[index] & ~bitmask;
 327 | 
 328 |     if (result != _buttons[index])
 329 |     {
 330 |         std::lock_guard<std::mutex> lock(_mutex);
 331 |         _buttons[index] = result;
 332 |     }
 333 | 
 334 |     if (_config.getAutoReport())
 335 |     {
 336 |         sendGamepadReport();
 337 |     }
 338 | }
 339 | 
 340 | uint8_t GamepadDevice::specialButtonBitPosition(uint8_t b)
 341 | {
 342 |     if (b >= POSSIBLESPECIALBUTTONS)
 343 |         throw std::invalid_argument("Index out of range");
 344 |     uint8_t bit = 0;
 345 |     for (int i = 0; i < b; i++)
 346 |     {
 347 |         if (_config.getWhichSpecialButtons()[i])
 348 |             bit++;
 349 |     }
 350 |     return bit;
 351 | }
 352 | 
 353 | void GamepadDevice::pressSpecialButton(uint8_t b)
 354 | {
 355 |     uint8_t button = specialButtonBitPosition(b);
 356 |     uint8_t bit = button % 8;
 357 |     uint8_t bitmask = (1 << bit);
 358 | 
 359 |     uint64_t result = _specialButtons | bitmask;
 360 | 
 361 |     if (result != _specialButtons)
 362 |     {
 363 |         std::lock_guard<std::mutex> lock(_mutex);
 364 |         _specialButtons = result;
 365 |     }
 366 | 
 367 |     if (_config.getAutoReport())
 368 |     {
 369 |         sendGamepadReport();
 370 |     }
 371 | }
 372 | 
 373 | void GamepadDevice::releaseSpecialButton(uint8_t b)
 374 | {
 375 |     uint8_t button = specialButtonBitPosition(b);
 376 |     uint8_t bit = button % 8;
 377 |     uint8_t bitmask = (1 << bit);
 378 | 
 379 |     uint64_t result = _specialButtons & ~bitmask;
 380 | 
 381 |     if (result != _specialButtons)
 382 |     {
 383 |         std::lock_guard<std::mutex> lock(_mutex);
 384 |         _specialButtons = result;
 385 |     }
 386 | 
 387 |     if (_config.getAutoReport())
 388 |     {
 389 |         sendGamepadReport();
 390 |     }
 391 | }
 392 | 
 393 | void GamepadDevice::pressStart()
 394 | {
 395 |     pressSpecialButton(START_BUTTON);
 396 | }
 397 | 
 398 | void GamepadDevice::releaseStart()
 399 | {
 400 |     releaseSpecialButton(START_BUTTON);
 401 | }
 402 | 
 403 | void GamepadDevice::pressSelect()
 404 | {
 405 |     pressSpecialButton(SELECT_BUTTON);
 406 | }
 407 | 
 408 | void GamepadDevice::releaseSelect()
 409 | {
 410 |     releaseSpecialButton(SELECT_BUTTON);
 411 | }
 412 | 
 413 | void GamepadDevice::pressMenu()
 414 | {
 415 |     pressSpecialButton(MENU_BUTTON);
 416 | }
 417 | 
 418 | void GamepadDevice::releaseMenu()
 419 | {
 420 |     releaseSpecialButton(MENU_BUTTON);
 421 | }
 422 | 
 423 | void GamepadDevice::pressHome()
 424 | {
 425 |     pressSpecialButton(HOME_BUTTON);
 426 | }
 427 | 
 428 | void GamepadDevice::releaseHome()
 429 | {
 430 |     releaseSpecialButton(HOME_BUTTON);
 431 | }
 432 | 
 433 | void GamepadDevice::pressBack()
 434 | {
 435 |     pressSpecialButton(BACK_BUTTON);
 436 | }
 437 | 
 438 | void GamepadDevice::releaseBack()
 439 | {
 440 |     releaseSpecialButton(BACK_BUTTON);
 441 | }
 442 | 
 443 | void GamepadDevice::pressVolumeInc()
 444 | {
 445 |     pressSpecialButton(VOLUME_INC_BUTTON);
 446 | }
 447 | 
 448 | void GamepadDevice::releaseVolumeInc()
 449 | {
 450 |     releaseSpecialButton(VOLUME_INC_BUTTON);
 451 | }
 452 | 
 453 | void GamepadDevice::pressVolumeDec()
 454 | {
 455 |     pressSpecialButton(VOLUME_DEC_BUTTON);
 456 | }
 457 | 
 458 | void GamepadDevice::releaseVolumeDec()
 459 | {
 460 |     releaseSpecialButton(VOLUME_DEC_BUTTON);
 461 | }
 462 | 
 463 | void GamepadDevice::pressVolumeMute()
 464 | {
 465 |     pressSpecialButton(VOLUME_MUTE_BUTTON);
 466 | }
 467 | 
 468 | void GamepadDevice::releaseVolumeMute()
 469 | {
 470 |     releaseSpecialButton(VOLUME_MUTE_BUTTON);
 471 | }
 472 | 
 473 | void GamepadDevice::setLeftThumb(int16_t x, int16_t y)
 474 | {
 475 |     if (x == -32768)
 476 |     {
 477 |         x = -32767;
 478 |     }
 479 |     if (y == -32768)
 480 |     {
 481 |         y = -32767;
 482 |     }
 483 | 
 484 |     {
 485 |         std::lock_guard<std::mutex> lock(_mutex);
 486 |         _x = x;
 487 |         _y = y;
 488 |     }
 489 | 
 490 |     if (_config.getAutoReport())
 491 |     {
 492 |         sendGamepadReport();
 493 |     }
 494 | }
 495 | 
 496 | void GamepadDevice::setRightThumb(int16_t z, int16_t rZ)
 497 | {
 498 |     if (z == -32768)
 499 |     {
 500 |         z = -32767;
 501 |     }
 502 |     if (rZ == -32768)
 503 |     {
 504 |         rZ = -32767;
 505 |     }
 506 | 
 507 |     {
 508 |         std::lock_guard<std::mutex> lock(_mutex);
 509 |         _z = z;
 510 |         _rZ = rZ;
 511 |     }
 512 | 
 513 |     if (_config.getAutoReport())
 514 |     {
 515 |         sendGamepadReport();
 516 |     }
 517 | }
 518 | 
 519 | void GamepadDevice::setLeftTrigger(int16_t rX)
 520 | {
 521 |     if (rX == -32768)
 522 |     {
 523 |         rX = -32767;
 524 |     }
 525 | 
 526 |     {
 527 |         std::lock_guard<std::mutex> lock(_mutex);
 528 |         _rX = rX;
 529 |     }
 530 | 
 531 |     if (_config.getAutoReport())
 532 |     {
 533 |         sendGamepadReport();
 534 |     }
 535 | }
 536 | 
 537 | void GamepadDevice::setRightTrigger(int16_t rY)
 538 | {
 539 |     if (rY == -32768)
 540 |     {
 541 |         rY = -32767;
 542 |     }
 543 | 
 544 |     {
 545 |         std::lock_guard<std::mutex> lock(_mutex);
 546 |         _rY = rY;
 547 |     }
 548 | 
 549 |     if (_config.getAutoReport())
 550 |     {
 551 |         sendGamepadReport();
 552 |     }
 553 | }
 554 | 
 555 | void GamepadDevice::setTriggers(int16_t rX, int16_t rY)
 556 | {
 557 |     if (rX == -32768)
 558 |     {
 559 |         rX = -32767;
 560 |     }
 561 |     if (rY == -32768)
 562 |     {
 563 |         rY = -32767;
 564 |     }
 565 | 
 566 |     {
 567 |         std::lock_guard<std::mutex> lock(_mutex);
 568 |         _rX = rX;
 569 |         _rY = rY;
 570 |     }
 571 | 
 572 |     if (_config.getAutoReport())
 573 |     {
 574 |         sendGamepadReport();
 575 |     }
 576 | }
 577 | 
 578 | void GamepadDevice::setHat(signed char hat)
 579 | {
 580 |     {
 581 |         std::lock_guard<std::mutex> lock(_mutex);
 582 |         _hat1 = hat;
 583 |     }
 584 | 
 585 |     if (_config.getAutoReport())
 586 |     {
 587 |         sendGamepadReport();
 588 |     }
 589 | }
 590 | 
 591 | void GamepadDevice::setHat1(signed char hat1)
 592 | {
 593 |     {
 594 |         std::lock_guard<std::mutex> lock(_mutex);
 595 |         _hat1 = hat1;
 596 |     }
 597 | 
 598 |     if (_config.getAutoReport())
 599 |     {
 600 |         sendGamepadReport();
 601 |     }
 602 | }
 603 | 
 604 | void GamepadDevice::setHat2(signed char hat2)
 605 | {
 606 |     {
 607 |         std::lock_guard<std::mutex> lock(_mutex);
 608 |         _hat2 = hat2;
 609 |     }
 610 | 
 611 |     if (_config.getAutoReport())
 612 |     {
 613 |         sendGamepadReport();
 614 |     }
 615 | }
 616 | 
 617 | void GamepadDevice::setHat3(signed char hat3)
 618 | {
 619 |     {
 620 |         std::lock_guard<std::mutex> lock(_mutex);
 621 |         _hat3 = hat3;
 622 |     }
 623 | 
 624 |     if (_config.getAutoReport())
 625 |     {
 626 |         sendGamepadReport();
 627 |     }
 628 | }
 629 | 
 630 | void GamepadDevice::setHat4(signed char hat4)
 631 | {
 632 |     {
 633 |         std::lock_guard<std::mutex> lock(_mutex);
 634 |         _hat4 = hat4;
 635 |     }
 636 | 
 637 | 
 638 |     if (_config.getAutoReport())
 639 |     {
 640 |         sendGamepadReport();
 641 |     }
 642 | }
 643 | 
 644 | void GamepadDevice::setX(int16_t x)
 645 | {
 646 |     if (x == -32768)
 647 |     {
 648 |         x = -32767;
 649 |     }
 650 | 
 651 |     {
 652 |         std::lock_guard<std::mutex> lock(_mutex);
 653 |         _x = x;
 654 |     }
 655 | 
 656 |     if (_config.getAutoReport())
 657 |     {
 658 |         sendGamepadReport();
 659 |     }
 660 | }
 661 | 
 662 | void GamepadDevice::setY(int16_t y)
 663 | {
 664 |     if (y == -32768)
 665 |     {
 666 |         y = -32767;
 667 |     }
 668 | 
 669 |     {
 670 |         std::lock_guard<std::mutex> lock(_mutex);
 671 |         _y = y;
 672 |     }
 673 | 
 674 |     if (_config.getAutoReport())
 675 |     {
 676 |         sendGamepadReport();
 677 |     }
 678 | }
 679 | 
 680 | void GamepadDevice::setZ(int16_t z)
 681 | {
 682 |     if (z == -32768)
 683 |     {
 684 |         z = -32767;
 685 |     }
 686 | 
 687 |     {
 688 |         std::lock_guard<std::mutex> lock(_mutex);
 689 |         _z = z;
 690 |     }
 691 | 
 692 |     if (_config.getAutoReport())
 693 |     {
 694 |         sendGamepadReport();
 695 |     }
 696 | }
 697 | 
 698 | void GamepadDevice::setRZ(int16_t rZ)
 699 | {
 700 |     if (rZ == -32768)
 701 |     {
 702 |         rZ = -32767;
 703 |     }
 704 | 
 705 |     {
 706 |         std::lock_guard<std::mutex> lock(_mutex);
 707 |         _rZ = rZ;
 708 |     }
 709 | 
 710 |     if (_config.getAutoReport())
 711 |     {
 712 |         sendGamepadReport();
 713 |     }
 714 | }
 715 | 
 716 | void GamepadDevice::setRX(int16_t rX)
 717 | {
 718 |     if (rX == -32768)
 719 |     {
 720 |         rX = -32767;
 721 |     }
 722 | 
 723 |     {
 724 |         std::lock_guard<std::mutex> lock(_mutex);
 725 |         _rX = rX;
 726 |     }
 727 | 
 728 |     if (_config.getAutoReport())
 729 |     {
 730 |         sendGamepadReport();
 731 |     }
 732 | }
 733 | 
 734 | void GamepadDevice::setRY(int16_t rY)
 735 | {
 736 |     if (rY == -32768)
 737 |     {
 738 |         rY = -32767;
 739 |     }
 740 | 
 741 |     {
 742 |         std::lock_guard<std::mutex> lock(_mutex);
 743 |         _rY = rY;
 744 |     }
 745 | 
 746 |     if (_config.getAutoReport())
 747 |     {
 748 |         sendGamepadReport();
 749 |     }
 750 | }
 751 | 
 752 | void GamepadDevice::setSlider(int16_t slider)
 753 | {
 754 |     if (slider == -32768)
 755 |     {
 756 |         slider = -32767;
 757 |     }
 758 | 
 759 |     {
 760 |         std::lock_guard<std::mutex> lock(_mutex);
 761 |         _slider1 = slider;
 762 |     }
 763 | 
 764 |     if (_config.getAutoReport())
 765 |     {
 766 |         sendGamepadReport();
 767 |     }
 768 | }
 769 | 
 770 | void GamepadDevice::setSlider1(int16_t slider1)
 771 | {
 772 |     if (slider1 == -32768)
 773 |     {
 774 |         slider1 = -32767;
 775 |     }
 776 | 
 777 |     {
 778 |         std::lock_guard<std::mutex> lock(_mutex);
 779 |         _slider1 = slider1;
 780 |     }
 781 | 
 782 |     if (_config.getAutoReport())
 783 |     {
 784 |         sendGamepadReport();
 785 |     }
 786 | }
 787 | 
 788 | void GamepadDevice::setSlider2(int16_t slider2)
 789 | {
 790 |     if (slider2 == -32768)
 791 |     {
 792 |         slider2 = -32767;
 793 |     }
 794 | 
 795 |     {
 796 |         std::lock_guard<std::mutex> lock(_mutex);
 797 |         _slider2 = slider2;
 798 |     }
 799 | 
 800 |     if (_config.getAutoReport())
 801 |     {
 802 |         sendGamepadReport();
 803 |     }
 804 | }
 805 | 
 806 | void GamepadDevice::setRudder(int16_t rudder)
 807 | {
 808 |     if (rudder == -32768)
 809 |     {
 810 |         rudder = -32767;
 811 |     }
 812 | 
 813 |     {
 814 |         std::lock_guard<std::mutex> lock(_mutex);
 815 |         _rudder = rudder;
 816 |     }
 817 | 
 818 |     if (_config.getAutoReport())
 819 |     {
 820 |         sendGamepadReport();
 821 |     }
 822 | }
 823 | 
 824 | void GamepadDevice::setThrottle(int16_t throttle)
 825 | {
 826 |     if (throttle == -32768)
 827 |     {
 828 |         throttle = -32767;
 829 |     }
 830 | 
 831 |     {
 832 |         std::lock_guard<std::mutex> lock(_mutex);
 833 |         _throttle = throttle;
 834 |     }
 835 | 
 836 |     if (_config.getAutoReport())
 837 |     {
 838 |         sendGamepadReport();
 839 |     }
 840 | }
 841 | 
 842 | void GamepadDevice::setAccelerator(int16_t accelerator)
 843 | {
 844 |     if (accelerator == -32768)
 845 |     {
 846 |         accelerator = -32767;
 847 |     }
 848 | 
 849 |     {
 850 |         std::lock_guard<std::mutex> lock(_mutex);
 851 |         _accelerator = accelerator;
 852 |     }
 853 | 
 854 |     if (_config.getAutoReport())
 855 |     {
 856 |         sendGamepadReport();
 857 |     }
 858 | }
 859 | 
 860 | void GamepadDevice::setBrake(int16_t brake)
 861 | {
 862 |     if (brake == -32768)
 863 |     {
 864 |         brake = -32767;
 865 |     }
 866 | 
 867 |     {
 868 |         std::lock_guard<std::mutex> lock(_mutex);
 869 |         _brake = brake;
 870 |     }
 871 | 
 872 |     if (_config.getAutoReport())
 873 |     {
 874 |         sendGamepadReport();
 875 |     }
 876 | }
 877 | 
 878 | void GamepadDevice::setSteering(int16_t steering)
 879 | {
 880 |     if (steering == -32768)
 881 |     {
 882 |         steering = -32767;
 883 |     }
 884 | 
 885 |     {
 886 |         std::lock_guard<std::mutex> lock(_mutex);
 887 |         _steering = steering;
 888 |     }
 889 | 
 890 |     if (_config.getAutoReport())
 891 |     {
 892 |         sendGamepadReport();
 893 |     }
 894 | }
 895 | 
 896 | bool GamepadDevice::isPressed(uint8_t b)
 897 | {
 898 |     uint8_t index = (b - 1) / 8;
 899 |     uint8_t bit = (b - 1) % 8;
 900 |     uint8_t bitmask = (1 << bit);
 901 | 
 902 |     if ((bitmask & _buttons[index]) > 0)
 903 |         return true;
 904 |     return false;
 905 | }
 906 | 
 907 | void GamepadDevice::sendGamepadReport(bool defer)
 908 | {
 909 |     if(defer || _config.getAutoReport()){
 910 |         queueDeferredReport(std::bind(&GamepadDevice::sendGamepadReportImp, this));
 911 |     } else {
 912 |         sendGamepadReportImp();
 913 |     }
 914 | }
 915 | 
 916 | void GamepadDevice::sendGamepadReportImp()
 917 | {
 918 |     auto input = getInput();
 919 |     auto parentDevice = this->getParent();
 920 | 
 921 |     if (!input || !parentDevice)
 922 |         return;
 923 | 
 924 |     if(!parentDevice->isConnected())
 925 |         return;
 926 | 
 927 |     uint8_t currentReportIndex = 0;
 928 |     uint8_t m[_config.getDeviceReportSize()];
 929 | 
 930 |     {
 931 |         // Lock the device input data
 932 |         std::lock_guard<std::mutex> lock(_mutex);
 933 | 
 934 |         memset(&m, 0, sizeof(m));
 935 |         memcpy(&m, &_buttons, sizeof(_buttons));
 936 |         
 937 |         currentReportIndex += _config.getButtonNumBytes();
 938 | 
 939 |         if (_config.getTotalSpecialButtonCount() > 0)
 940 |         {
 941 |             m[currentReportIndex++] = _specialButtons;
 942 |         }
 943 | 
 944 |         if (_config.getIncludeXAxis())
 945 |         {
 946 |             m[currentReportIndex++] = _x;
 947 |             m[currentReportIndex++] = (_x >> 8);
 948 |         }
 949 |         if (_config.getIncludeYAxis())
 950 |         {
 951 |             m[currentReportIndex++] = _y;
 952 |             m[currentReportIndex++] = (_y >> 8);
 953 |         }
 954 |         if (_config.getIncludeZAxis())
 955 |         {
 956 |             m[currentReportIndex++] = _z;
 957 |             m[currentReportIndex++] = (_z >> 8);
 958 |         }
 959 |         if (_config.getIncludeRzAxis())
 960 |         {
 961 |             m[currentReportIndex++] = _rZ;
 962 |             m[currentReportIndex++] = (_rZ >> 8);
 963 |         }
 964 |         if (_config.getIncludeRxAxis())
 965 |         {
 966 |             m[currentReportIndex++] = _rX;
 967 |             m[currentReportIndex++] = (_rX >> 8);
 968 |         }
 969 |         if (_config.getIncludeRyAxis())
 970 |         {
 971 |             m[currentReportIndex++] = _rY;
 972 |             m[currentReportIndex++] = (_rY >> 8);
 973 |         }
 974 | 
 975 |         if (_config.getIncludeSlider1())
 976 |         {
 977 |             m[currentReportIndex++] = _slider1;
 978 |             m[currentReportIndex++] = (_slider1 >> 8);
 979 |         }
 980 |         if (_config.getIncludeSlider2())
 981 |         {
 982 |             m[currentReportIndex++] = _slider2;
 983 |             m[currentReportIndex++] = (_slider2 >> 8);
 984 |         }
 985 | 
 986 |         if (_config.getIncludeRudder())
 987 |         {
 988 |             m[currentReportIndex++] = _rudder;
 989 |             m[currentReportIndex++] = (_rudder >> 8);
 990 |         }
 991 |         if (_config.getIncludeThrottle())
 992 |         {
 993 |             m[currentReportIndex++] = _throttle;
 994 |             m[currentReportIndex++] = (_throttle >> 8);
 995 |         }
 996 |         if (_config.getIncludeAccelerator())
 997 |         {
 998 |             m[currentReportIndex++] = _accelerator;
 999 |             m[currentReportIndex++] = (_accelerator >> 8);
1000 |         }
1001 |         if (_config.getIncludeBrake())
1002 |         {
1003 |             m[currentReportIndex++] = _brake;
1004 |             m[currentReportIndex++] = (_brake >> 8);
1005 |         }
1006 |         if (_config.getIncludeSteering())
1007 |         {
1008 |             m[currentReportIndex++] = _steering;
1009 |             m[currentReportIndex++] = (_steering >> 8);
1010 |         }
1011 | 
1012 |         if (_config.getHatSwitchCount() > 0)
1013 |         {
1014 |             signed char hats[4];
1015 | 
1016 |             hats[0] = _hat1;
1017 |             hats[1] = _hat2;
1018 |             hats[2] = _hat3;
1019 |             hats[3] = _hat4;
1020 | 
1021 |             for (int currentHatIndex = _config.getHatSwitchCount() - 1; currentHatIndex >= 0; currentHatIndex--)
1022 |             {
1023 |                 m[currentReportIndex++] = hats[currentHatIndex];
1024 |             }
1025 |         }
1026 |     }
1027 | 
1028 |     // Notify
1029 |     input->setValue(m, sizeof(m));
1030 |     input->notify();
1031 | }
1032 | 


--------------------------------------------------------------------------------
/GamepadDevice.h:
--------------------------------------------------------------------------------
  1 | #ifndef ESP32_GAMEPAD_DEVICE_H
  2 | #define ESP32_GAMEPAD_DEVICE_H
  3 | 
  4 | #include <NimBLECharacteristic.h>
  5 | #include <GamepadConfiguration.h>
  6 | #include <BaseCompositeDevice.h>
  7 | #include <Callback.h>
  8 | #include <mutex>
  9 | 
 10 | // Forwards
 11 | class GamepadDevice;
 12 | 
 13 | 
 14 | class GamepadCallbacks : public NimBLECharacteristicCallbacks
 15 | {
 16 | public:
 17 |     GamepadCallbacks(GamepadDevice* device);
 18 | 
 19 |     void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
 20 |     void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
 21 |     void onStatus(NimBLECharacteristic* pCharacteristic, int code) override;
 22 |     void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override;
 23 | 
 24 | private:
 25 |     GamepadDevice* _device;
 26 | };
 27 | 
 28 | 
 29 | class GamepadDevice : public BaseCompositeDevice
 30 | {
 31 |     friend class GamepadCallbacks;
 32 | private:
 33 |     GamepadConfiguration _config;
 34 | 
 35 |     // Gamepad
 36 |     uint8_t _buttons[16]; // 8 bits x 16 --> 128 bits
 37 |     uint8_t _specialButtons;
 38 |     int16_t _x;
 39 |     int16_t _y;
 40 |     int16_t _z;
 41 |     int16_t _rZ;
 42 |     int16_t _rX;
 43 |     int16_t _rY;
 44 |     int16_t _slider1;
 45 |     int16_t _slider2;
 46 |     int16_t _rudder;
 47 |     int16_t _throttle;
 48 |     int16_t _accelerator;
 49 |     int16_t _brake;
 50 |     int16_t _steering;
 51 |     int16_t _hat1;
 52 |     int16_t _hat2;
 53 |     int16_t _hat3;
 54 |     int16_t _hat4;
 55 | 
 56 |     GamepadCallbacks* _callbacks;
 57 | 
 58 | public:
 59 |     GamepadDevice();
 60 |     GamepadDevice(const GamepadConfiguration& config);
 61 |     ~GamepadDevice();
 62 | 
 63 |     void init(NimBLEHIDDevice* hid) override;
 64 |     const BaseCompositeDeviceConfiguration* getDeviceConfig() const override;
 65 | 
 66 |     void setAxes(int16_t x = 0, int16_t y = 0, int16_t z = 0, int16_t rZ = 0, int16_t rX = 0, int16_t rY = 0, int16_t slider1 = 0, int16_t slider2 = 0);
 67 |     void press(uint8_t b = BUTTON_1);   // press BUTTON_1 by default
 68 |     void release(uint8_t b = BUTTON_1); // release BUTTON_1 by default
 69 |     void pressSpecialButton(uint8_t b);
 70 |     void releaseSpecialButton(uint8_t b);
 71 |     void pressStart();
 72 |     void releaseStart();
 73 |     void pressSelect();
 74 |     void releaseSelect();
 75 |     void pressMenu();
 76 |     void releaseMenu();
 77 |     void pressHome();
 78 |     void releaseHome();
 79 |     void pressBack();
 80 |     void releaseBack();
 81 |     void pressVolumeInc();
 82 |     void releaseVolumeInc();
 83 |     void pressVolumeDec();
 84 |     void releaseVolumeDec();
 85 |     void pressVolumeMute();
 86 |     void releaseVolumeMute();
 87 |     void setLeftThumb(int16_t x = 0, int16_t y = 0);
 88 |     void setRightThumb(int16_t z = 0, int16_t rZ = 0);
 89 |     void setLeftTrigger(int16_t rX = 0);
 90 |     void setRightTrigger(int16_t rY = 0);
 91 |     void setTriggers(int16_t rX = 0, int16_t rY = 0);
 92 |     void setHats(signed char hat1 = 0, signed char hat2 = 0, signed char hat3 = 0, signed char hat4 = 0);
 93 |     void setHat(signed char hat = 0);
 94 |     void setHat1(signed char hat1 = 0);
 95 |     void setHat2(signed char hat2 = 0);
 96 |     void setHat3(signed char hat3 = 0);
 97 |     void setHat4(signed char hat4 = 0);
 98 |     void setX(int16_t x = 0);
 99 |     void setY(int16_t y = 0);
100 |     void setZ(int16_t z = 0);
101 |     void setRZ(int16_t rZ = 0);
102 |     void setRX(int16_t rX = 0);
103 |     void setRY(int16_t rY = 0);
104 |     void setSliders(int16_t slider1 = 0, int16_t slider2 = 0);
105 |     void setSlider(int16_t slider = 0);
106 |     void setSlider1(int16_t slider1 = 0);
107 |     void setSlider2(int16_t slider2 = 0);
108 |     void setRudder(int16_t rudder = 0);
109 |     void setThrottle(int16_t throttle = 0);
110 |     void setAccelerator(int16_t accelerator = 0);
111 |     void setBrake(int16_t brake = 0);
112 |     void setSteering(int16_t steering = 0);
113 |     void setSimulationControls(int16_t rudder = 0, int16_t throttle = 0, int16_t accelerator = 0, int16_t brake = 0, int16_t steering = 0);
114 |     
115 |     bool isPressed(uint8_t b = BUTTON_1); // check BUTTON_1 by default
116 |     bool isConnected(void);
117 |     void resetButtons();
118 | 
119 |     void sendGamepadReport(bool defer = false);
120 | private:
121 |     void sendGamepadReportImp();
122 | 
123 | 
124 |     // Output properties
125 |     uint8_t getPlayerIndicator();
126 | 
127 |     // callbacks
128 |     Signal<uint8_t> onPlayerIndicatorChanged;
129 | 
130 | 
131 | private:
132 |     uint8_t specialButtonBitPosition(uint8_t specialButton);
133 | 
134 |     // Output properties
135 |     uint8_t playerIndicator;
136 | 
137 |     // Threaded access
138 |     std::mutex _mutex;
139 | 
140 |     // NimBLECharacteristic* _setEffectCharacteristic;
141 |     // NimBLECharacteristic* _setEnvelopeCharacteristic;
142 |     // NimBLECharacteristic* _setConditionCharacteristic;
143 |     // NimBLECharacteristic* _setPeriodicCharacteristic;
144 |     // NimBLECharacteristic* _setConstantCharacteristic;
145 |     // NimBLECharacteristic* _setRampCharacteristic;
146 |     // NimBLECharacteristic* _setCustomForceCharacteristic;
147 |     // NimBLECharacteristic* _downloadForceCharacteristic;
148 |     // NimBLECharacteristic* _effectOperationCharacteristic;
149 |     // NimBLECharacteristic* _pidDeviceControlCharacteristic;
150 |     // NimBLECharacteristic* _deviceGainCharacteristic;
151 |     // NimBLECharacteristic* _customForceCharacteristic;
152 |     // NimBLECharacteristic* _pidState;
153 |     // NimBLECharacteristic* _createNewEffect;
154 |     // NimBLECharacteristic* _pidBlockLoad;
155 |     // NimBLECharacteristic* _pidPool;
156 | };
157 | 
158 | #endif


--------------------------------------------------------------------------------
/KeyboardConfiguration.cpp:
--------------------------------------------------------------------------------
 1 | #include "KeyboardConfiguration.h"
 2 | #include "KeyboardDevice.h"
 3 | #include "KeyboardDescriptors.h"
 4 | 
 5 | KeyboardConfiguration::KeyboardConfiguration() :
 6 |     BaseCompositeDeviceConfiguration(KEYBOARD_REPORT_ID),
 7 |     _useMediaKeys(false)
 8 | {
 9 | }
10 | 
11 | KeyboardConfiguration::KeyboardConfiguration(uint8_t reportId) :
12 |     BaseCompositeDeviceConfiguration(reportId),
13 |     _useMediaKeys(false)
14 | {
15 | }
16 | 
17 | uint8_t KeyboardConfiguration::getDeviceReportSize() const
18 | {
19 |     return sizeof(_keyboardHIDReportDescriptor);
20 | }
21 | 
22 | size_t KeyboardConfiguration::makeDeviceReport(uint8_t* buffer, size_t bufferSize) const
23 | {
24 |     size_t hidDescriptorSize = sizeof(_keyboardHIDReportDescriptor);
25 |     if(hidDescriptorSize < bufferSize){
26 |         memcpy(buffer, _keyboardHIDReportDescriptor, hidDescriptorSize);
27 |     } else {
28 |         return -1;
29 |     }
30 | 
31 |     if(_useMediaKeys){
32 |         size_t mediaKeysHidDescriptorSize = sizeof(_mediakeysHIDReportDescriptor);
33 |         if(hidDescriptorSize + mediaKeysHidDescriptorSize < bufferSize){
34 |             memcpy(buffer + hidDescriptorSize, _mediakeysHIDReportDescriptor, mediaKeysHidDescriptorSize);
35 |             hidDescriptorSize += mediaKeysHidDescriptorSize;
36 |         } else {
37 |             return -1;
38 |         }
39 |     }
40 |     
41 |     return hidDescriptorSize;
42 | }
43 | 
44 | bool KeyboardConfiguration::getUseMediaKeys() const
45 | {
46 |     return _useMediaKeys;
47 | }
48 | 
49 | void KeyboardConfiguration::setUseMediaKeys(bool value)
50 | {
51 |     _useMediaKeys = value;
52 | }


--------------------------------------------------------------------------------
/KeyboardConfiguration.h:
--------------------------------------------------------------------------------
 1 | #ifndef KEYBOARD_CONFIGURATION_H
 2 | #define KEYBOARD_CONFIGURATION_H
 3 | 
 4 | #include <BaseCompositeDevice.h>
 5 | 
 6 | class KeyboardConfiguration : public BaseCompositeDeviceConfiguration
 7 | {   
 8 | public:
 9 |     KeyboardConfiguration();
10 |     KeyboardConfiguration(uint8_t reportId);
11 |     uint8_t getDeviceReportSize() const override;
12 |     size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override;
13 | 
14 |     bool getUseMediaKeys() const;
15 |     void setUseMediaKeys(bool value);
16 | private:
17 |     bool _useMediaKeys;
18 | };
19 | 
20 | #endif


--------------------------------------------------------------------------------
/KeyboardDescriptors.h:
--------------------------------------------------------------------------------
 1 | #ifndef KEYBOARDDESCRIPTORS_H
 2 | #define KEYBOARDDESCRIPTORS_H
 3 | 
 4 | #include <HIDTypes.h>
 5 | 
 6 | #define KEYBOARD_REPORT_ID 0x40
 7 | #define MEDIA_KEYS_REPORT_ID 0x43
 8 | 
 9 | static const uint8_t _keyboardHIDReportDescriptor[] = {
10 |   // Input
11 |   USAGE_PAGE(1),      0x01,                     // USAGE_PAGE (Generic Desktop Ctrls)
12 |   USAGE(1),           0x06,                     // USAGE (Keyboard)
13 |   COLLECTION(1),      0x01,                     // COLLECTION (Application)
14 |   REPORT_ID(1),       KEYBOARD_REPORT_ID,       //   REPORT_ID (1)
15 |   USAGE_PAGE(1),      0x07,                     //   USAGE_PAGE (Kbrd/Keypad)
16 |   USAGE_MINIMUM(1),   0xE0,                     //   USAGE_MINIMUM (0xE0)
17 |   USAGE_MAXIMUM(1),   0xE7,                     //   USAGE_MAXIMUM (0xE7)
18 |   LOGICAL_MINIMUM(1), 0x00,                     //   LOGICAL_MINIMUM (0)
19 |   LOGICAL_MAXIMUM(1), 0x01,                     //   Logical Maximum (1)
20 |   REPORT_SIZE(1),     0x01,                     //   REPORT_SIZE (1)
21 |   REPORT_COUNT(1),    0x08,                     //   REPORT_COUNT (8)
22 |   HIDINPUT(1),        0x02,                     //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) ; Modifier byte
23 |   REPORT_COUNT(1),    0x01,                     //   REPORT_COUNT (1) ; 1 byte (Reserved)
24 |   REPORT_SIZE(1),     0x08,                     //   REPORT_SIZE (8)
25 |   HIDINPUT(1),        0x01,                     //   INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) ; Reserved byte
26 |   REPORT_COUNT(1),    0x06,                     //   REPORT_COUNT (6) ; 6 bytes (Keys)
27 |   REPORT_SIZE(1),     0x08,                     //   REPORT_SIZE(8)
28 |   LOGICAL_MINIMUM(1), 0x00,                     //   LOGICAL_MINIMUM(0)
29 |   LOGICAL_MAXIMUM(1), 0x65,                     //   LOGICAL_MAXIMUM(0x65) ; 101 keys
30 |   USAGE_PAGE(1),      0x07,                     //   USAGE_PAGE (Kbrd/Keypad)
31 |   USAGE_MINIMUM(1),   0x00,                     //   USAGE_MINIMUM (0)
32 |   USAGE_MAXIMUM(1),   0x65,                     //   USAGE_MAXIMUM (0x65)
33 |   HIDINPUT(1),        0x00,                     //   INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) ; Key arrays (6 bytes)
34 |   // Output
35 |   REPORT_COUNT(1),    0x05,                     //   REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
36 |   REPORT_SIZE(1),     0x01,                     //   REPORT_SIZE (1)
37 |   USAGE_PAGE(1),      0x08,                     //   USAGE_PAGE (LEDs)
38 |   USAGE_MINIMUM(1),   0x01,                     //   USAGE_MINIMUM (0x01) ; Num Lock
39 |   USAGE_MAXIMUM(1),   0x05,                     //   USAGE_MAXIMUM (0x05) ; Kana
40 |   HIDOUTPUT(1),       0x02,                     //   OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
41 |   REPORT_COUNT(1),    0x01,                     //   REPORT_COUNT (1) ; 3 bits (Padding)
42 |   REPORT_SIZE(1),     0x03,                     //   REPORT_SIZE (3)
43 |   HIDOUTPUT(1),       0x01,                     //   OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
44 |   END_COLLECTION(0),                            // END_COLLECTION
45 | };
46 | 
47 | static const uint8_t _mediakeysHIDReportDescriptor[] = {
48 |   USAGE_PAGE(1),      0x0C,                         // USAGE_PAGE (Consumer)
49 |   USAGE(1),           0x01,                         // USAGE (Consumer Control)
50 |   COLLECTION(1),      0x01,                         // COLLECTION (Application)
51 |   REPORT_ID(1),       MEDIA_KEYS_REPORT_ID,         //   REPORT_ID (3)
52 |   USAGE_PAGE(1),      0x0C,                         //   USAGE_PAGE (Consumer)
53 |   LOGICAL_MINIMUM(1), 0x00,                         //   LOGICAL_MINIMUM (0)
54 |   LOGICAL_MAXIMUM(1), 0x01,                         //   LOGICAL_MAXIMUM (1)
55 |   REPORT_SIZE(1),     0x01,                         //   REPORT_SIZE (1)
56 |   REPORT_COUNT(1),    0x18,                         //   REPORT_COUNT (24)
57 |   USAGE(1),           0xB0,                         //   USAGE (Play)
58 |   USAGE(1),           0xB1,                         //   USAGE (Pause)
59 |   USAGE(1),           0xB2,                         //   USAGE (Record)
60 |   USAGE(1),           0xB3,                         //   USAGE (Fast Forward)
61 |   USAGE(1),           0xB4,                         //   USAGE (Rewind)
62 |   USAGE(1),           0xB5,                         //   USAGE (Scan Next Track)      
63 |   USAGE(1),           0xB6,                         //   USAGE (Scan Previous Track)  
64 |   USAGE(1),           0xB7,                         //   USAGE (Stop)                
65 |   USAGE(1),           0xB8,                         //   USAGE (Eject)    
66 |   USAGE(1),           0xB9,                         //   USAGE (Random Play)         
67 |   USAGE(1),           0xBC,                         //   USAGE (Repeat)
68 |   USAGE(1),           0xCD,                         //   USAGE (Play/Pause)          
69 |   USAGE(1),           0xE2,                         //   USAGE (Mute)                
70 |   USAGE(1),           0xE9,                         //   USAGE (Volume Increment)    
71 |   USAGE(1),           0xEA,                         //   USAGE (Volume Decrement)    
72 |   // 2 byte usages  
73 |   USAGE(2),           0x23, 0x02,                   //   Usage (WWW Home)
74 |   USAGE(2),           0x94, 0x01,                   //   Usage (My Computer) 
75 |   USAGE(2),           0x92, 0x01,                   //   Usage (Calculator)  
76 |   USAGE(2),           0x2A, 0x02,                   //   Usage (WWW fav)     
77 |   USAGE(2),           0x21, 0x02,                   //   Usage (WWW search)  
78 |   USAGE(2),           0x26, 0x02,                   //   Usage (WWW stop)    
79 |   USAGE(2),           0x24, 0x02,                   //   Usage (WWW back)    
80 |   USAGE(2),           0x83, 0x01,                   //   Usage (Media sel)   
81 |   USAGE(2),           0x8A, 0x01,                   //   Usage (Mail)        
82 |   HIDINPUT(1),        0x02,                         //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
83 |   END_COLLECTION(0)                                 // END_COLLECTION
84 | };
85 | 
86 | 
87 | #endif // KEYBOARDDESCRIPTORS_H
88 | 


--------------------------------------------------------------------------------
/KeyboardDevice.cpp:
--------------------------------------------------------------------------------
  1 | #include "KeyboardDevice.h"
  2 | #include "KeyboardDescriptors.h"
  3 | #include "BleCompositeHID.h"
  4 | 
  5 | #if defined(CONFIG_ARDUHAL_ESP_LOG)
  6 | #include "esp32-hal-log.h"
  7 | #define LOG_TAG "KeyboardDevice"
  8 | #else
  9 | #include "esp_log.h"
 10 | static const char *LOG_TAG = "KeyboardDevice";
 11 | #endif
 12 | 
 13 | KeyboardCallbacks::KeyboardCallbacks(KeyboardDevice* device) :
 14 |     _device(device)
 15 | {
 16 | }
 17 | 
 18 | void KeyboardCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo)
 19 | {
 20 |     KeyboardOutputReport ledReport = pCharacteristic->getValue<uint8_t>();
 21 |     ESP_LOGD(LOG_TAG, "KeyboardDevice::onWrite - LED Report: %d", ledReport);
 22 |     _device->onLED.fire(ledReport);
 23 | }
 24 | 
 25 | void KeyboardCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo)
 26 | {
 27 | }
 28 | 
 29 | void KeyboardCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue)
 30 | {
 31 | }
 32 | 
 33 | void KeyboardCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code)
 34 | {
 35 | }
 36 | 
 37 | KeyboardDevice::KeyboardDevice() :
 38 |     _config(KeyboardConfiguration(KEYBOARD_REPORT_ID)),
 39 |     _input(),
 40 |     _output(),
 41 |     _inputReport(),
 42 |     _callbacks(nullptr)
 43 | {
 44 |     resetKeys();
 45 | }
 46 | 
 47 | KeyboardDevice::KeyboardDevice(const KeyboardConfiguration& config) :
 48 |     _config(config),
 49 |     _input(),
 50 |     _output(),
 51 |     _inputReport(),
 52 |     _callbacks(nullptr)
 53 | {
 54 |     resetKeys();
 55 | }
 56 | 
 57 | KeyboardDevice::~KeyboardDevice()
 58 | {
 59 |     if (getOutput() && _callbacks){
 60 |         getOutput()->setCallbacks(nullptr);
 61 |         delete _callbacks;
 62 |         _callbacks = nullptr;
 63 |     }
 64 | }
 65 | 
 66 | void KeyboardDevice::init(NimBLEHIDDevice* hid)
 67 | {
 68 |     _input = hid->inputReport(_config.getReportId());
 69 |     _mediaInput = hid->inputReport(MEDIA_KEYS_REPORT_ID);
 70 |     _output = hid->outputReport(_config.getReportId());
 71 |     _callbacks = new KeyboardCallbacks(this);
 72 |     _output->setCallbacks(_callbacks);
 73 | 
 74 |     setCharacteristics(_input, _output);
 75 | }
 76 | 
 77 | const BaseCompositeDeviceConfiguration* KeyboardDevice::getDeviceConfig() const
 78 | {
 79 |     return &_config;
 80 | }
 81 | 
 82 | void KeyboardDevice::resetKeys()
 83 | {
 84 |     std::lock_guard<std::mutex> lock(_mutex);
 85 |     _inputReport.modifiers = 0x00;
 86 |     _inputReport.reserved = 0x00;
 87 |     memset(&_inputReport.keys, KEY_NONE, sizeof(_inputReport.keys));
 88 |     _mediaKeyInputReport.keys = 0x000000;
 89 | 
 90 | }
 91 | 
 92 | void KeyboardDevice::modifierKeyPress(uint8_t modifier)
 93 | {
 94 |     {
 95 |         std::lock_guard<std::mutex> lock(_mutex);
 96 |         _inputReport.modifiers |= modifier;
 97 |     }
 98 | 
 99 |     if (_config.getAutoReport())
100 |     {
101 |         sendKeyReport();
102 |     }
103 | }
104 | 
105 | void KeyboardDevice::modifierKeyRelease(uint8_t modifier)
106 | {
107 |     {
108 |         std::lock_guard<std::mutex> lock(_mutex);
109 |         _inputReport.modifiers ^= modifier;
110 |     }
111 | 
112 |     if (_config.getAutoReport())
113 |     {
114 |         sendKeyReport();
115 |     }
116 | }
117 | 
118 | void KeyboardDevice::mediaKeyPress(uint32_t mediaKey)
119 | {
120 |     {
121 |         std::lock_guard<std::mutex> lock(_mutex);
122 |         _mediaKeyInputReport.keys |= mediaKey;
123 |     }
124 | 
125 |     if (_config.getAutoReport())
126 |     {
127 |         sendMediaKeyReport();
128 |     }
129 | }
130 | 
131 | void KeyboardDevice::mediaKeyRelease(uint32_t mediaKey)
132 | {
133 |     {
134 |         std::lock_guard<std::mutex> lock(_mutex);
135 |         _mediaKeyInputReport.keys ^= mediaKey;
136 |     }
137 | 
138 |     if (_config.getAutoReport())
139 |     {
140 |         sendMediaKeyReport();
141 |     }
142 | }
143 | 
144 | void KeyboardDevice::keyPress(uint8_t keyCode)
145 | {
146 |     // Find the first empty slot
147 |     bool full = true;
148 |     for (int slotIdx = 0; slotIdx < 6; slotIdx++)
149 |     {
150 |         if (_inputReport.keys[slotIdx] == 0x00)
151 |         {
152 |             full = false;
153 |             std::lock_guard<std::mutex> lock(_mutex);
154 |             _inputReport.keys[slotIdx] = keyCode;
155 |             break;
156 |         }
157 |     }
158 | 
159 |     // If no slots are free, set the overflow flag
160 |     if(full){
161 |         std::lock_guard<std::mutex> lock(_mutex);
162 |         memset(_inputReport.keys, KEY_ERR_OVF, sizeof(_inputReport.keys));
163 |     }
164 | 
165 |     if (_config.getAutoReport())
166 |     {
167 |         sendKeyReport();
168 |     }
169 | }
170 | 
171 | void KeyboardDevice::keyRelease(uint8_t keyCode)
172 | {
173 |     for (int slotIdx = 0; slotIdx < 6; slotIdx++)
174 |     {
175 |         if (_inputReport.keys[slotIdx] == keyCode)
176 |         {
177 |             std::lock_guard<std::mutex> lock(_mutex);
178 |             _inputReport.keys[slotIdx] = 0x00;
179 |             break;
180 |         }
181 |     }
182 | 
183 |     if (_config.getAutoReport())
184 |     {
185 |         sendKeyReport();
186 |     }
187 | }
188 | 
189 | void KeyboardDevice::sendKeyReport(bool defer)
190 | {
191 |     if(defer || _config.getAutoDefer()){
192 |         queueDeferredReport(std::bind(&KeyboardDevice::sendKeyReportImpl, this));
193 |     } else {
194 |         sendKeyReportImpl();
195 |     }
196 | }
197 | 
198 | void KeyboardDevice::sendKeyReportImpl()
199 | {
200 |     auto input = getInput();
201 |     auto parentDevice = this->getParent();
202 | 
203 |     if (!input || !parentDevice)
204 |         return;
205 | 
206 |     if(!parentDevice->isConnected())
207 |         return;
208 | 
209 |     uint8_t currentReportIndex = 0;
210 |     uint8_t m[_config.getDeviceReportSize()];
211 |     memset(&m, 0, sizeof(m));
212 | 
213 |     // Copy key input report into buffer
214 |     {
215 |         std::lock_guard<std::mutex> lock(_mutex);
216 |         memcpy(&m[currentReportIndex], &_inputReport, sizeof(_inputReport));
217 |         input->setValue((uint8_t*)&_inputReport, sizeof(_inputReport));
218 |     }
219 |     input->notify();
220 | }
221 | 
222 | void KeyboardDevice::sendMediaKeyReport(bool defer)
223 | {
224 |     if(defer || _config.getAutoDefer()){
225 |         queueDeferredReport(std::bind(&KeyboardDevice::sendMediaKeyReportImpl, this));
226 |     } else {
227 |         sendMediaKeyReportImpl();
228 |     }
229 | }
230 | 
231 | 
232 | void KeyboardDevice::sendMediaKeyReportImpl()
233 | {
234 |    auto input = getInput();
235 |     auto parentDevice = this->getParent();
236 | 
237 |     if (!input || !parentDevice)
238 |         return;
239 | 
240 |     if(!parentDevice->isConnected())
241 |         return;
242 | 
243 |     uint8_t m[3];
244 |     memset(&m, 0, sizeof(m));
245 | 
246 |     // Copy key input report into buffer
247 |     {
248 |         std::lock_guard<std::mutex> lock(_mutex);
249 |         m[0] = _mediaKeyInputReport.keys & 0xFF;
250 |         m[1] = (_mediaKeyInputReport.keys >> 8) & 0xFF;
251 |         m[2] = (_mediaKeyInputReport.keys >> 16) & 0xFF;
252 | 
253 |         _mediaInput->setValue((uint8_t*)&m, sizeof(m));
254 |     }
255 |     _mediaInput->notify();
256 | }


--------------------------------------------------------------------------------
/KeyboardDevice.h:
--------------------------------------------------------------------------------
 1 | #ifndef ESP32_KEYBOARD_DEVICE_H
 2 | #define ESP32_KEYBOARD_DEVICE_H
 3 | 
 4 | #include "NimBLECharacteristic.h"
 5 | #include <KeyboardHIDCodes.h>
 6 | #include <KeyboardConfiguration.h>
 7 | #include <BaseCompositeDevice.h>
 8 | #include <Callback.h>
 9 | #include <mutex>
10 | 
11 | struct KeyboardInputReport {
12 |     uint8_t modifiers = 0x00;
13 |     uint8_t reserved = 0x00;
14 |     uint8_t keys[6]; // 8 bits per key * 101 keys = 6 bytes
15 | };
16 | 
17 | struct KeyboardMediaInputReport {
18 |     uint32_t keys;
19 | };
20 | 
21 | struct KeyboardOutputReport {
22 |     bool numLockActive;
23 |     bool capsLockActive;
24 |     bool scrollLockActive;
25 |     bool composeActive;
26 |     bool kanaActive;
27 | 
28 |     constexpr KeyboardOutputReport(uint8_t value = 0) noexcept : 
29 |         numLockActive(value & KEY_LED_NUMLOCK),
30 |         capsLockActive(value & KEY_LED_CAPSLOCK),
31 |         scrollLockActive(value & KEY_LED_SCROLLLOCK),
32 |         composeActive(value & KEY_LED_COMPOSE),
33 |         kanaActive(value & KEY_LED_KANA)
34 |     {}
35 | };
36 | 
37 | // Forwards 
38 | class KeyboardDevice;
39 | 
40 | class KeyboardCallbacks : public NimBLECharacteristicCallbacks {
41 | public:
42 |     KeyboardCallbacks(KeyboardDevice* device);
43 | 
44 |     void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
45 |     void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
46 |     void onStatus(NimBLECharacteristic* pCharacteristic, int code) override;
47 |     void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override;
48 | 
49 | private:
50 |     KeyboardDevice* _device;
51 | };
52 | 
53 | 
54 | class KeyboardDevice : public BaseCompositeDevice {
55 | private:
56 |     KeyboardConfiguration _config;
57 |     NimBLECharacteristic* _input;
58 |     NimBLECharacteristic* _mediaInput;
59 |     NimBLECharacteristic* _output;
60 | 
61 |     KeyboardInputReport _inputReport;
62 |     KeyboardMediaInputReport _mediaKeyInputReport;
63 |     KeyboardCallbacks* _callbacks;
64 | 
65 | public:
66 |     KeyboardDevice();
67 |     KeyboardDevice(const KeyboardConfiguration& config);
68 |     ~KeyboardDevice();
69 |     
70 |     void init(NimBLEHIDDevice* hid) override;
71 |     const BaseCompositeDeviceConfiguration* getDeviceConfig() const override;
72 | 
73 |     void resetKeys();
74 | 
75 |     void keyPress(uint8_t keyCode);
76 |     void keyRelease(uint8_t keyCode);
77 |     void modifierKeyPress(uint8_t modifier);
78 |     void modifierKeyRelease(uint8_t modifier);
79 |     void mediaKeyPress(uint32_t mediaKey);
80 |     void mediaKeyRelease(uint32_t mediaKey);
81 | 
82 |     Signal<KeyboardOutputReport> onLED;
83 | 
84 |     void sendKeyReport(bool defer = false);
85 |     void sendMediaKeyReport(bool defer = false);
86 | 
87 | private:
88 |     void sendKeyReportImpl();
89 |     void sendMediaKeyReportImpl();
90 | 
91 |     // Threading
92 |     std::mutex _mutex;
93 | };
94 | 
95 | #endif


--------------------------------------------------------------------------------
/KeyboardHIDCodes.h:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * USB HID Keyboard scan codes as per USB spec 1.11
  3 |  * plus some additional codes
  4 |  * 
  5 |  * Created by MightyPork, 2016
  6 |  * Public domain
  7 |  * 
  8 |  * Adapted from:
  9 |  * https://source.android.com/devices/input/keyboard-devices.html
 10 |  */
 11 | 
 12 | #ifndef USB_HID_KEYS
 13 | #define USB_HID_KEYS
 14 | 
 15 | /**
 16 |  * Modifier masks - used for the first byte in the HID report.
 17 |  * NOTE: The second byte in the report is reserved, 0x00
 18 |  */
 19 | #define KEY_MOD_LCTRL  0x01
 20 | #define KEY_MOD_LSHIFT 0x02
 21 | #define KEY_MOD_LALT   0x04
 22 | #define KEY_MOD_LMETA  0x08
 23 | #define KEY_MOD_RCTRL  0x10
 24 | #define KEY_MOD_RSHIFT 0x20
 25 | #define KEY_MOD_RALT   0x40
 26 | #define KEY_MOD_RMETA  0x80
 27 | 
 28 | /**
 29 |  * Scan codes - last N slots in the HID report (usually 6).
 30 |  * 0x00 if no key pressed.
 31 |  * 
 32 |  * If more than N keys are pressed, the HID reports 
 33 |  * KEY_ERR_OVF in all slots to indicate this condition.
 34 |  */
 35 | 
 36 | #define KEY_NONE 0x00 // No key pressed
 37 | #define KEY_ERR_OVF 0x01 //  Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
 38 | // 0x02 //  Keyboard POST Fail
 39 | // 0x03 //  Keyboard Error Undefined
 40 | #define KEY_A 0x04 // Keyboard a and A
 41 | #define KEY_B 0x05 // Keyboard b and B
 42 | #define KEY_C 0x06 // Keyboard c and C
 43 | #define KEY_D 0x07 // Keyboard d and D
 44 | #define KEY_E 0x08 // Keyboard e and E
 45 | #define KEY_F 0x09 // Keyboard f and F
 46 | #define KEY_G 0x0a // Keyboard g and G
 47 | #define KEY_H 0x0b // Keyboard h and H
 48 | #define KEY_I 0x0c // Keyboard i and I
 49 | #define KEY_J 0x0d // Keyboard j and J
 50 | #define KEY_K 0x0e // Keyboard k and K
 51 | #define KEY_L 0x0f // Keyboard l and L
 52 | #define KEY_M 0x10 // Keyboard m and M
 53 | #define KEY_N 0x11 // Keyboard n and N
 54 | #define KEY_O 0x12 // Keyboard o and O
 55 | #define KEY_P 0x13 // Keyboard p and P
 56 | #define KEY_Q 0x14 // Keyboard q and Q
 57 | #define KEY_R 0x15 // Keyboard r and R
 58 | #define KEY_S 0x16 // Keyboard s and S
 59 | #define KEY_T 0x17 // Keyboard t and T
 60 | #define KEY_U 0x18 // Keyboard u and U
 61 | #define KEY_V 0x19 // Keyboard v and V
 62 | #define KEY_W 0x1a // Keyboard w and W
 63 | #define KEY_X 0x1b // Keyboard x and X
 64 | #define KEY_Y 0x1c // Keyboard y and Y
 65 | #define KEY_Z 0x1d // Keyboard z and Z
 66 | 
 67 | #define KEY_1 0x1e // Keyboard 1 and !
 68 | #define KEY_2 0x1f // Keyboard 2 and @
 69 | #define KEY_3 0x20 // Keyboard 3 and #
 70 | #define KEY_4 0x21 // Keyboard 4 and $
 71 | #define KEY_5 0x22 // Keyboard 5 and %
 72 | #define KEY_6 0x23 // Keyboard 6 and ^
 73 | #define KEY_7 0x24 // Keyboard 7 and &
 74 | #define KEY_8 0x25 // Keyboard 8 and *
 75 | #define KEY_9 0x26 // Keyboard 9 and (
 76 | #define KEY_0 0x27 // Keyboard 0 and )
 77 | 
 78 | #define KEY_ENTER 0x28 // Keyboard Return (ENTER)
 79 | #define KEY_ESC 0x29 // Keyboard ESCAPE
 80 | #define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
 81 | #define KEY_TAB 0x2b // Keyboard Tab
 82 | #define KEY_SPACE 0x2c // Keyboard Spacebar
 83 | #define KEY_MINUS 0x2d // Keyboard - and _
 84 | #define KEY_EQUAL 0x2e // Keyboard = and +
 85 | #define KEY_LEFTBRACE 0x2f // Keyboard [ and {
 86 | #define KEY_RIGHTBRACE 0x30 // Keyboard ] and }
 87 | #define KEY_BACKSLASH 0x31 // Keyboard \ and |
 88 | #define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~
 89 | #define KEY_SEMICOLON 0x33 // Keyboard ; and :
 90 | #define KEY_APOSTROPHE 0x34 // Keyboard ' and "
 91 | #define KEY_GRAVE 0x35 // Keyboard ` and ~
 92 | #define KEY_COMMA 0x36 // Keyboard , and <
 93 | #define KEY_DOT 0x37 // Keyboard . and >
 94 | #define KEY_SLASH 0x38 // Keyboard / and ?
 95 | #define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock
 96 | 
 97 | #define KEY_F1 0x3a // Keyboard F1
 98 | #define KEY_F2 0x3b // Keyboard F2
 99 | #define KEY_F3 0x3c // Keyboard F3
100 | #define KEY_F4 0x3d // Keyboard F4
101 | #define KEY_F5 0x3e // Keyboard F5
102 | #define KEY_F6 0x3f // Keyboard F6
103 | #define KEY_F7 0x40 // Keyboard F7
104 | #define KEY_F8 0x41 // Keyboard F8
105 | #define KEY_F9 0x42 // Keyboard F9
106 | #define KEY_F10 0x43 // Keyboard F10
107 | #define KEY_F11 0x44 // Keyboard F11
108 | #define KEY_F12 0x45 // Keyboard F12
109 | 
110 | #define KEY_SYSRQ 0x46 // Keyboard Print Screen
111 | #define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock
112 | #define KEY_PAUSE 0x48 // Keyboard Pause
113 | #define KEY_INSERT 0x49 // Keyboard Insert
114 | #define KEY_HOME 0x4a // Keyboard Home
115 | #define KEY_PAGEUP 0x4b // Keyboard Page Up
116 | #define KEY_DELETE 0x4c // Keyboard Delete Forward
117 | #define KEY_END 0x4d // Keyboard End
118 | #define KEY_PAGEDOWN 0x4e // Keyboard Page Down
119 | #define KEY_RIGHT 0x4f // Keyboard Right Arrow
120 | #define KEY_LEFT 0x50 // Keyboard Left Arrow
121 | #define KEY_DOWN 0x51 // Keyboard Down Arrow
122 | #define KEY_UP 0x52 // Keyboard Up Arrow
123 | 
124 | #define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear
125 | #define KEY_KPSLASH 0x54 // Keypad /
126 | #define KEY_KPASTERISK 0x55 // Keypad *
127 | #define KEY_KPMINUS 0x56 // Keypad -
128 | #define KEY_KPPLUS 0x57 // Keypad +
129 | #define KEY_KPENTER 0x58 // Keypad ENTER
130 | #define KEY_KP1 0x59 // Keypad 1 and End
131 | #define KEY_KP2 0x5a // Keypad 2 and Down Arrow
132 | #define KEY_KP3 0x5b // Keypad 3 and PageDn
133 | #define KEY_KP4 0x5c // Keypad 4 and Left Arrow
134 | #define KEY_KP5 0x5d // Keypad 5
135 | #define KEY_KP6 0x5e // Keypad 6 and Right Arrow
136 | #define KEY_KP7 0x5f // Keypad 7 and Home
137 | #define KEY_KP8 0x60 // Keypad 8 and Up Arrow
138 | #define KEY_KP9 0x61 // Keypad 9 and Page Up
139 | #define KEY_KP0 0x62 // Keypad 0 and Insert
140 | #define KEY_KPDOT 0x63 // Keypad . and Delete
141 | 
142 | #define KEY_102ND 0x64 // Keyboard Non-US \ and |
143 | #define KEY_COMPOSE 0x65 // Keyboard Application
144 | #define KEY_POWER 0x66 // Keyboard Power
145 | #define KEY_KPEQUAL 0x67 // Keypad =
146 | 
147 | #define KEY_F13 0x68 // Keyboard F13
148 | #define KEY_F14 0x69 // Keyboard F14
149 | #define KEY_F15 0x6a // Keyboard F15
150 | #define KEY_F16 0x6b // Keyboard F16
151 | #define KEY_F17 0x6c // Keyboard F17
152 | #define KEY_F18 0x6d // Keyboard F18
153 | #define KEY_F19 0x6e // Keyboard F19
154 | #define KEY_F20 0x6f // Keyboard F20
155 | #define KEY_F21 0x70 // Keyboard F21
156 | #define KEY_F22 0x71 // Keyboard F22
157 | #define KEY_F23 0x72 // Keyboard F23
158 | #define KEY_F24 0x73 // Keyboard F24
159 | 
160 | #define KEY_OPEN 0x74 // Keyboard Execute
161 | #define KEY_HELP 0x75 // Keyboard Help
162 | #define KEY_PROPS 0x76 // Keyboard Menu
163 | #define KEY_FRONT 0x77 // Keyboard Select
164 | #define KEY_STOP 0x78 // Keyboard Stop
165 | #define KEY_AGAIN 0x79 // Keyboard Again
166 | #define KEY_UNDO 0x7a // Keyboard Undo
167 | #define KEY_CUT 0x7b // Keyboard Cut
168 | #define KEY_COPY 0x7c // Keyboard Copy
169 | #define KEY_PASTE 0x7d // Keyboard Paste
170 | #define KEY_FIND 0x7e // Keyboard Find
171 | #define KEY_MUTE 0x7f // Keyboard Mute
172 | #define KEY_VOLUMEUP 0x80 // Keyboard Volume Up
173 | #define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down
174 | // 0x82  Keyboard Locking Caps Lock
175 | // 0x83  Keyboard Locking Num Lock
176 | // 0x84  Keyboard Locking Scroll Lock
177 | #define KEY_KPCOMMA 0x85 // Keypad Comma
178 | // 0x86  Keypad Equal Sign
179 | #define KEY_RO 0x87 // Keyboard International1
180 | #define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2
181 | #define KEY_YEN 0x89 // Keyboard International3
182 | #define KEY_HENKAN 0x8a // Keyboard International4
183 | #define KEY_MUHENKAN 0x8b // Keyboard International5
184 | #define KEY_KPJPCOMMA 0x8c // Keyboard International6
185 | // 0x8d  Keyboard International7
186 | // 0x8e  Keyboard International8
187 | // 0x8f  Keyboard International9
188 | #define KEY_HANGEUL 0x90 // Keyboard LANG1
189 | #define KEY_HANJA 0x91 // Keyboard LANG2
190 | #define KEY_KATAKANA 0x92 // Keyboard LANG3
191 | #define KEY_HIRAGANA 0x93 // Keyboard LANG4
192 | #define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5
193 | // 0x95  Keyboard LANG6
194 | // 0x96  Keyboard LANG7
195 | // 0x97  Keyboard LANG8
196 | // 0x98  Keyboard LANG9
197 | // 0x99  Keyboard Alternate Erase
198 | // 0x9a  Keyboard SysReq/Attention
199 | // 0x9b  Keyboard Cancel
200 | // 0x9c  Keyboard Clear
201 | // 0x9d  Keyboard Prior
202 | // 0x9e  Keyboard Return
203 | // 0x9f  Keyboard Separator
204 | // 0xa0  Keyboard Out
205 | // 0xa1  Keyboard Oper
206 | // 0xa2  Keyboard Clear/Again
207 | // 0xa3  Keyboard CrSel/Props
208 | // 0xa4  Keyboard ExSel
209 | 
210 | // 0xb0  Keypad 00
211 | // 0xb1  Keypad 000
212 | // 0xb2  Thousands Separator
213 | // 0xb3  Decimal Separator
214 | // 0xb4  Currency Unit
215 | // 0xb5  Currency Sub-unit
216 | #define KEY_KPLEFTPAREN 0xb6 // Keypad (
217 | #define KEY_KPRIGHTPAREN 0xb7 // Keypad )
218 | // 0xb8  Keypad {
219 | // 0xb9  Keypad }
220 | // 0xba  Keypad Tab
221 | // 0xbb  Keypad Backspace
222 | // 0xbc  Keypad A
223 | // 0xbd  Keypad B
224 | // 0xbe  Keypad C
225 | // 0xbf  Keypad D
226 | // 0xc0  Keypad E
227 | // 0xc1  Keypad F
228 | // 0xc2  Keypad XOR
229 | // 0xc3  Keypad ^
230 | // 0xc4  Keypad %
231 | // 0xc5  Keypad <
232 | // 0xc6  Keypad >
233 | // 0xc7  Keypad &
234 | // 0xc8  Keypad &&
235 | // 0xc9  Keypad |
236 | // 0xca  Keypad ||
237 | // 0xcb  Keypad :
238 | // 0xcc  Keypad #
239 | // 0xcd  Keypad Space
240 | // 0xce  Keypad @
241 | // 0xcf  Keypad !
242 | // 0xd0  Keypad Memory Store
243 | // 0xd1  Keypad Memory Recall
244 | // 0xd2  Keypad Memory Clear
245 | // 0xd3  Keypad Memory Add
246 | // 0xd4  Keypad Memory Subtract
247 | // 0xd5  Keypad Memory Multiply
248 | // 0xd6  Keypad Memory Divide
249 | // 0xd7  Keypad +/-
250 | // 0xd8  Keypad Clear
251 | // 0xd9  Keypad Clear Entry
252 | // 0xda  Keypad Binary
253 | // 0xdb  Keypad Octal
254 | // 0xdc  Keypad Decimal
255 | // 0xdd  Keypad Hexadecimal
256 | 
257 | #define KEY_LEFTCTRL 0xe0 // Keyboard Left Control
258 | #define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift
259 | #define KEY_LEFTALT 0xe2 // Keyboard Left Alt
260 | #define KEY_LEFTMETA 0xe3 // Keyboard Left GUI
261 | #define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control
262 | #define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift
263 | #define KEY_RIGHTALT 0xe6 // Keyboard Right Alt
264 | #define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI
265 | 
266 | // Media key bitflags
267 | #define KEY_MEDIA_PLAY 0x1
268 | #define KEY_MEDIA_PAUSE 0x2
269 | #define KEY_MEDIA_RECORD 0x4
270 | #define KEY_MEDIA_FASTFORWARD 0x8
271 | #define KEY_MEDIA_REWIND 0x10
272 | #define KEY_MEDIA_NEXTTRACK 0x20
273 | #define KEY_MEDIA_PREVIOUSTRACK 0x40
274 | #define KEY_MEDIA_STOP 0x80
275 | #define KEY_MEDIA_EJECT 0x100
276 | #define KEY_MEDIA_RANDOMPLAY 0x200
277 | #define KEY_MEDIA_REPEAT 0x400
278 | #define KEY_MEDIA_PLAYPAUSE 0x800
279 | #define KEY_MEDIA_MUTE 0x1000
280 | #define KEY_MEDIA_VOLUMEUP 0x2000
281 | #define KEY_MEDIA_VOLUMEDOWN 0x4000
282 | #define KEY_MEDIA_WWWHOME 0x8000
283 | #define KEY_MEDIA_MYCOMPUTER 0x10000
284 | #define KEY_MEDIA_CALCULATOR 0x20000
285 | #define KEY_MEDIA_WWWFAVORITES 0x40000
286 | #define KEY_MEDIA_WWWSEARCH 0x80000
287 | #define KEY_MEDIA_WWWSTOP 0x100000
288 | #define KEY_MEDIA_WWWBACK 0x200000
289 | #define KEY_MEDIA_MEDIASELECT 0x400000
290 | #define KEY_MEDIA_MAIL 0x800000
291 | 
292 | // LED bitflags
293 | #define KEY_LED_NUMLOCK 0x1
294 | #define KEY_LED_CAPSLOCK 0x2
295 | #define KEY_LED_SCROLLLOCK 0x4
296 | #define KEY_LED_COMPOSE 0x8
297 | #define KEY_LED_KANA 0x10
298 | 
299 | #endif // USB_HID_KEYS
300 | 


--------------------------------------------------------------------------------
/MouseConfiguration.cpp:
--------------------------------------------------------------------------------
  1 | #include "MouseConfiguration.h"
  2 | 
  3 | MouseConfiguration::MouseConfiguration() : 
  4 |     BaseCompositeDeviceConfiguration(MOUSE_REPORT_ID),
  5 |     _mouseButtonCount(5),
  6 |     _whichAxes{true, true, false, false, false, false, true, true}
  7 | {               
  8 | }
  9 | 
 10 | const char* MouseConfiguration::getDeviceName() const { 
 11 |     return MOUSE_DEVICE_NAME; 
 12 | }
 13 | 
 14 | uint8_t MouseConfiguration::getDeviceReportSize() const
 15 | {
 16 |     // TODO: Make number of mouse buttons dynamic
 17 |     uint8_t numOfMouseButtonBytes = this->getMouseButtonNumBytes(); // 5 buttons @ 1 bit each means we need 3 bits of padding to pad to a byte
 18 | 
 19 |     // TODO: Make number of mouse axis' dynamic? 
 20 |     uint8_t numOfMouseAxisBytes = this->getMouseAxisCount(); //X, Y, Wheel, Horiz wheel;
 21 | 
 22 |     // Mouse report size (bytes)
 23 |     uint8_t mouseReportSize = numOfMouseButtonBytes + numOfMouseAxisBytes;
 24 |     return mouseReportSize;
 25 | }
 26 | 
 27 | size_t MouseConfiguration::makeDeviceReport(uint8_t* buffer, size_t bufferSize) const
 28 | {
 29 |     uint8_t tempHidReportDescriptor[150];
 30 |     int hidReportDescriptorSize = 0;
 31 | 
 32 |     // Mouse setup
 33 |     tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1);       
 34 |     tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; //Generic Desktop
 35 | 
 36 |     tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); 
 37 |     tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02; //Mouse
 38 | 
 39 |     tempHidReportDescriptor[hidReportDescriptorSize++] = COLLECTION(1);
 40 |     tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; //Application
 41 | 
 42 |     tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1);
 43 |     tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; //Pointer
 44 | 
 45 |     tempHidReportDescriptor[hidReportDescriptorSize++] = COLLECTION(1);
 46 |     tempHidReportDescriptor[hidReportDescriptorSize++] = 0x0; //Physical
 47 | 
 48 |     tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_ID(1);
 49 |     tempHidReportDescriptor[hidReportDescriptorSize++] = this->getReportId(); //Mouse report ID
 50 |     
 51 |     // Buttons (Left, Right, Middle, Back, Forward)
 52 |     if (this->getMouseButtonCount() > 0)
 53 |     {
 54 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1);
 55 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09; //USAGE_PAGE (Button)
 56 | 
 57 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_MINIMUM(1);
 58 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; //Button 1
 59 | 
 60 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_MAXIMUM(1);
 61 |         tempHidReportDescriptor[hidReportDescriptorSize++] = this->getMouseButtonCount();
 62 | 
 63 |         tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MINIMUM(1);
 64 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
 65 | 
 66 |         tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MAXIMUM(1);
 67 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
 68 | 
 69 |         tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1);
 70 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
 71 | 
 72 |         tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1);
 73 |         tempHidReportDescriptor[hidReportDescriptorSize++] = this->getMouseButtonCount();
 74 | 
 75 |         tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1);
 76 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02; //INPUT (Data, Variable, Absolute) ;5 button bits
 77 |         
 78 |         uint8_t mouseButtonPaddingBits = getMouseButtonPaddingBits();
 79 |         if (mouseButtonPaddingBits > 0)
 80 |         {
 81 |             // 5 buttons @ 1 bit each means we need 3 bits of padding to pad to a byte
 82 |             // The number of reports matches the number of bits needed to pad out to a byte
 83 |             tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1);
 84 |             tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
 85 | 
 86 |             tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1);
 87 |             tempHidReportDescriptor[hidReportDescriptorSize++] = mouseButtonPaddingBits;
 88 | 
 89 |             tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1);
 90 |             tempHidReportDescriptor[hidReportDescriptorSize++] = 0x03; //INPUT (Constant, Variable, Absolute)
 91 |         }
 92 |     }
 93 | 
 94 |     if (this->getMouseAxisCount() > 0)
 95 |     {
 96 |         // X/Y position, Wheel
 97 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1);
 98 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; //Generic Desktop
 99 | 
100 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1);
101 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x30; // X coordinate
102 | 
103 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1);
104 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x031; // Y coordinate
105 | 
106 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1);
107 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x38; // Wheel
108 | 
109 |         tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MINIMUM(1);
110 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81; // Logical Min (-127)
111 | 
112 |         tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MAXIMUM(1);
113 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x7f; // Logical Max (127)
114 | 
115 |         tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1);
116 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x08; // Report Size (8). Whole byte, no padding needed
117 | 
118 |         tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1);
119 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x03; // Report Count (3). 3 bytes total
120 | 
121 |         tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1);
122 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x06; // Input (Data, Variable, Relative) ;3 bytes (X,Y,Wheel)
123 |     
124 |         // Horizontal wheel
125 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1);
126 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x0c; //Consumer Devices
127 | 
128 |         tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(2);
129 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x38; //AC Pan
130 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
131 | 
132 |         tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MINIMUM(1);
133 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81; // Logical Min (-127)
134 | 
135 |         tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MAXIMUM(1);
136 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x7f; // Logical Max (127)
137 | 
138 |         tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1);
139 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x08; // Report Size (8). Whole byte, no padding needed
140 | 
141 |         tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1);
142 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; // Report Count (1). 1 byte total
143 | 
144 |         tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1);
145 |         tempHidReportDescriptor[hidReportDescriptorSize++] = 0x06; // Input (Data, Variable, Relative) ;1 byte (Horizontal wheel)
146 |     }
147 | 
148 |     // End Collection (Application - Physical)
149 |     tempHidReportDescriptor[hidReportDescriptorSize++] = END_COLLECTION(0); //0xc0;
150 | 
151 |     // END_COLLECTION (Application)
152 |     tempHidReportDescriptor[hidReportDescriptorSize++] = END_COLLECTION(0); //0xc0;
153 | 
154 | 
155 |     if(hidReportDescriptorSize < bufferSize){
156 |         memcpy(buffer, tempHidReportDescriptor, hidReportDescriptorSize);
157 |     } else {
158 |         return -1;
159 |     }
160 | 
161 |     return hidReportDescriptorSize;
162 | }
163 | 
164 | uint16_t MouseConfiguration::getMouseButtonCount() const { return _mouseButtonCount; }
165 | uint16_t MouseConfiguration::getMouseAxisCount() const { 
166 |     int count = 0;
167 |     for (int i = 0; i < MOUSE_POSSIBLE_AXIS_COUNT; i++)
168 |     {
169 |         count += (int)_whichAxes[i];
170 |     }
171 | 
172 |     return count;
173 | }
174 | 
175 | void MouseConfiguration::setMouseButtonCount(uint16_t value) { _mouseButtonCount = value; }
176 | 
177 | uint8_t MouseConfiguration::getMouseButtonPaddingBits() const
178 | {
179 |     uint8_t mouseButtonPaddingBits = 8 - (this->getMouseButtonCount() % 8);
180 |     if (mouseButtonPaddingBits == 8)
181 |     {
182 |         mouseButtonPaddingBits = 0;
183 |     }
184 | 
185 |     return mouseButtonPaddingBits;
186 | }
187 | 
188 | uint8_t MouseConfiguration::getMouseButtonNumBytes() const
189 | {
190 |     uint8_t numOfMouseButtonBytes = this->getMouseButtonCount() / 8;
191 |     if (getMouseButtonPaddingBits() > 0)
192 |     {
193 |         numOfMouseButtonBytes++;
194 |     }
195 | 
196 |     return numOfMouseButtonBytes;
197 | }
198 | 


--------------------------------------------------------------------------------
/MouseConfiguration.h:
--------------------------------------------------------------------------------
 1 | #ifndef ESP32_BLE_MOUSE_CONFIG_H
 2 | #define ESP32_BLE_MOUSE_CONFIG_H
 3 | 
 4 | #include <BaseCompositeDevice.h>
 5 | #include <MouseConfiguration.h>
 6 | 
 7 | #define MOUSE_REPORT_ID 0x20
 8 | #define MOUSE_DEVICE_NAME "Mouse"
 9 | 
10 | #define MOUSE_LOGICAL_LEFT_BUTTON 0x01
11 | #define MOUSE_LOGICAL_RIGHT_BUTTON 0x02
12 | #define MOUSE_LOGICAL_BUTTON_3 0x03
13 | #define MOUSE_LOGICAL_BUTTON_4 0x04
14 | #define MOUSE_LOGICAL_BUTTON_5 0x05
15 | 
16 | #define MOUSE_DEFAULT_AXIS_COUNT 2
17 | #define MOUSE_POSSIBLE_AXIS_COUNT 8
18 | 
19 | // Keyboard
20 | 
21 | class MouseConfiguration : public BaseCompositeDeviceConfiguration
22 | {
23 | private:
24 |     uint16_t _buttonCount;
25 |     bool _whichAxes[MOUSE_POSSIBLE_AXIS_COUNT];
26 |     uint16_t _mouseButtonCount;
27 | 
28 | public:
29 |     MouseConfiguration();
30 | 
31 |     const char* getDeviceName() const override;
32 |     uint8_t getDeviceReportSize() const override;
33 |     size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override;
34 |     uint8_t getMouseButtonNumBytes() const;
35 | 
36 |     uint16_t getMouseButtonCount() const;
37 |     uint16_t getMouseAxisCount() const;
38 | 
39 |     void setMouseButtonCount(uint16_t value);
40 | 
41 | private:
42 |     uint8_t getMouseButtonPaddingBits() const;
43 | };
44 | 
45 | #endif
46 | 


--------------------------------------------------------------------------------
/MouseDevice.cpp:
--------------------------------------------------------------------------------
  1 | #include "MouseDevice.h"
  2 | #include "BleCompositeHID.h"
  3 | 
  4 | MouseDevice::MouseDevice():
  5 |     _config(MouseConfiguration()), // Use default config
  6 |     _mouseButtons(),
  7 |     _mouseX(0),
  8 |     _mouseY(0),
  9 |     _mouseWheel(0),
 10 |     _mouseHWheel(0)
 11 | {
 12 |     this->resetButtons();
 13 | }
 14 | 
 15 | MouseDevice::MouseDevice(const MouseConfiguration& config):
 16 |     _config(config), // Copy config to avoid modification
 17 |     _mouseButtons(),
 18 |     _mouseX(0),
 19 |     _mouseY(0),
 20 |     _mouseWheel(0),
 21 |     _mouseHWheel(0)
 22 | {
 23 |     this->resetButtons();
 24 | }
 25 | 
 26 | void MouseDevice::init(NimBLEHIDDevice* hid)
 27 | {
 28 |     setCharacteristics(hid->inputReport(_config.getReportId()), nullptr);
 29 | }
 30 | 
 31 | const BaseCompositeDeviceConfiguration* MouseDevice::getDeviceConfig() const
 32 | {
 33 |     return &_config;
 34 | }
 35 | 
 36 | void MouseDevice::resetButtons()
 37 | {
 38 |     std::lock_guard<std::mutex> lock(_mutex);
 39 |     memset(&_mouseButtons, 0, sizeof(_mouseButtons));
 40 | }
 41 | 
 42 | // Mouse
 43 | void MouseDevice::mouseClick(uint8_t button)
 44 | {
 45 |     // No-op
 46 |     // TODO: Send two reports one after the other for convience? Can't be both pressed and not pressed in a bitflag
 47 | }
 48 | 
 49 | void MouseDevice::mousePress(uint8_t button)
 50 | {
 51 |     uint8_t index = (button - 1) / 8;
 52 |     uint8_t bit = (button - 1) % 8;
 53 |     uint8_t bitmask = (1 << bit);
 54 | 
 55 |     uint8_t result = _mouseButtons[index] | bitmask;
 56 | 
 57 |     if (result != _mouseButtons[index])
 58 |     {
 59 |         std::lock_guard<std::mutex> lock(_mutex);
 60 |         _mouseButtons[index] = result;
 61 |     }
 62 | 
 63 |     if (_config.getAutoReport())
 64 |     {
 65 |         sendMouseReport();
 66 |     }
 67 | }
 68 | 
 69 | void MouseDevice::mouseRelease(uint8_t button)
 70 | {
 71 |     uint8_t index = (button - 1) / 8;
 72 |     uint8_t bit = (button - 1) % 8;
 73 |     uint8_t bitmask = (1 << bit);
 74 | 
 75 |     uint64_t result = _mouseButtons[index] & ~bitmask;
 76 | 
 77 |     if (result != _mouseButtons[index])
 78 |     {
 79 |         std::lock_guard<std::mutex> lock(_mutex);
 80 |         _mouseButtons[index] = result;
 81 |     }
 82 | 
 83 |     if (_config.getAutoReport())
 84 |     {
 85 |         sendMouseReport();
 86 |     }
 87 | }
 88 | 
 89 | void MouseDevice::mouseMove(signed char x, signed char y, signed char scrollX, signed char scrollY)
 90 | {
 91 |     if (x == -127)
 92 |     {
 93 |         x = -126;
 94 |     }
 95 |     if (y == -127)
 96 |     {
 97 |         y = -126;
 98 |     }
 99 |     if (scrollX == -127)
100 |     {
101 |         scrollX = -126;
102 |     }
103 |     if (scrollY == -127)
104 |     {
105 |         scrollY = -126;
106 |     }
107 | 
108 |     {
109 |         std::lock_guard<std::mutex> lock(_mutex);
110 |         _mouseX = x;
111 |         _mouseY = y;
112 |         _mouseWheel = scrollY;
113 |         _mouseHWheel = scrollX;
114 |     }
115 | 
116 |     if (_config.getAutoReport())
117 |     {
118 |         sendMouseReport();
119 |     }
120 | }
121 | 
122 | void MouseDevice::sendMouseReport(bool defer)
123 | {
124 |     if (defer || _config.getAutoDefer())
125 |     {
126 |         queueDeferredReport(std::bind(&MouseDevice::sendMouseReportImpl, this));
127 |     }
128 |     else
129 |     {
130 |         sendMouseReportImpl();
131 |     }
132 | }
133 | 
134 | void MouseDevice::sendMouseReportImpl()
135 | {
136 |     auto input = getInput();
137 |     auto parentDevice = this->getParent();
138 | 
139 |     if (!input || !parentDevice)
140 |         return;
141 |     
142 |     if(!parentDevice->isConnected())
143 |         return;
144 | 
145 |     uint8_t mouse_report[_config.getDeviceReportSize()];
146 |     uint8_t currentReportIndex = 0;
147 | 
148 |     { 
149 |         std::lock_guard<std::mutex> lock(_mutex);
150 |         
151 |         memset(&mouse_report, 0, sizeof(mouse_report));
152 |         memcpy(&mouse_report, &_mouseButtons, sizeof(_mouseButtons));
153 |         currentReportIndex += _config.getMouseButtonNumBytes();
154 | 
155 |         // TODO: Make dynamic based on axis counts
156 |         if (_config.getMouseAxisCount() > 0)
157 |         {
158 |             mouse_report[currentReportIndex++] = _mouseX;
159 |             mouse_report[currentReportIndex++] = _mouseY;
160 |             mouse_report[currentReportIndex++] = _mouseWheel;
161 |             mouse_report[currentReportIndex++] = _mouseHWheel;
162 |         }
163 |     }
164 | 
165 |     input->setValue(mouse_report, sizeof(mouse_report));
166 |     input->notify();
167 | }


--------------------------------------------------------------------------------
/MouseDevice.h:
--------------------------------------------------------------------------------
 1 | 
 2 | #ifndef ESP32_MOUSE_DEVICE_H
 3 | #define ESP32_MOUSE_DEVICE_H
 4 | 
 5 | #include "NimBLECharacteristic.h"
 6 | #include <MouseConfiguration.h>
 7 | #include <BaseCompositeDevice.h>
 8 | #include <mutex>
 9 | 
10 | class MouseDevice : public BaseCompositeDevice {
11 | private:
12 |     MouseConfiguration _config;
13 |     NimBLECharacteristic* _input;
14 |     NimBLECharacteristic* _output;
15 | 
16 |     uint8_t _mouseButtons[16]; // 8 bits x 16 --> 128 bits
17 |     signed char _mouseX;
18 |     signed char _mouseY;
19 |     signed char _mouseWheel;
20 |     signed char _mouseHWheel;
21 | 
22 | public:
23 |     MouseDevice();
24 |     MouseDevice(const MouseConfiguration& config);
25 |     
26 |     void init(NimBLEHIDDevice* hid) override;
27 |     const BaseCompositeDeviceConfiguration* getDeviceConfig() const override;
28 | 
29 |     void resetButtons();
30 |     void mouseClick(uint8_t button = MOUSE_LOGICAL_LEFT_BUTTON);
31 |     void mousePress(uint8_t button = MOUSE_LOGICAL_LEFT_BUTTON);
32 |     void mouseRelease(uint8_t button = MOUSE_LOGICAL_LEFT_BUTTON);
33 |     void mouseMove(signed char x, signed char y, signed char scrollX = 0, signed char scrollY = 0);
34 |     
35 |     void sendMouseReport(bool defer = false);
36 | 
37 | private:
38 |     void sendMouseReportImpl();
39 | 
40 |     // Threading
41 |     std::mutex _mutex;
42 | };
43 | 
44 | #endif


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # ESP32-BLE-CompositeHID
  2 | 
  3 | Forked from ESP32-BLE-Gamepad by lemmingDev to provide support support for composite human interface devices.
  4 | 
  5 | This library will let your ESP32 microcontroller behave as a bluetooth mouse, keyboard, gamepad (XInput or generic), or a combination of any of these devices.
  6 | 
  7 | ## License
  8 | Published under the MIT license. Please see license.txt.
  9 | 
 10 | ## Dependencies
 11 |  - Nimble-Arduino. Use the latest Github version from commit [a79941c](https://github.com/h2zero/NimBLE-Arduino/commit/a79941cc9e77ed60136ee385b1c73d89c847bd94) onwards. Do not use 1.4.2 from the Arduino library manager.
 12 |  -  Callback. Available from the Arduino library manager or [here](https://github.com/tomstewart89/Callback).
 13 | 
 14 | ## XInput gamepad features
 15 | 
 16 |  - [x] All buttons and joystick axes available
 17 |  - [x] XBox One S and XBox Series X controller support
 18 |  - [x] Linux XInput support (Kernel version < 6.5 only supports the XBox One S controller)
 19 |  - [x] Haptic feedback callbacks for strong and weak motor rumble support
 20 |  - [ ] LED support (pull requests welcome)
 21 | 
 22 | ## Generic gamepad features (from ESP32-BLE-Gamepad)
 23 | 
 24 |  - [x] Button press (128 buttons)
 25 |  - [x] Button release (128 buttons)
 26 |  - [x] Axes movement (6 axes (configurable resolution up to 16 bit) (x, y, z, rZ, rX, rY) --> (Left Thumb X, Left Thumb Y, Right Thumb X, Right Thumb Y, Left Trigger, Right Trigger))
 27 |  - [x] 2 Sliders (configurable resolution up to 16 bit) (Slider 1 and Slider 2)
 28 |  - [x] 4 point of view hats (ie. d-pad plus 3 other hat switches)
 29 |  - [x] Simulation controls (rudder, throttle, accelerator, brake, steering)
 30 |  - [x] Special buttons (start, select, menu, home, back, volume up, volume down, volume mute) all disabled by default
 31 | 
 32 | ## Mouse features
 33 |  - [x] Configurable button count
 34 |  - [x] X and Y axes
 35 |  - [ ] Configurable axes
 36 | 
 37 | ## Keyboard features
 38 |  - [x] Supports most USB HID scancodes
 39 |  - [x] Media key support
 40 |  - [x] LED callbacks for caps/num/scroll lock keys
 41 | 
 42 | ## Composite BLE host features (adapted from ESP32-BLE-Gamepad)
 43 |  - [x] Configurable HID descriptors per device
 44 |  - [x] Configurable VID and PID values
 45 |  - [x] Configurable BLE characteristics (name, manufacturer, model number, software revision, serial number, firmware revision, hardware revision)	
 46 |  - [x] Report optional battery level to host
 47 |  - [x] Uses efficient NimBLE bluetooth library
 48 |  - [x] Compatible with Windows
 49 |  - [x] Compatible with Android (Android OS maps default buttons / axes / hats slightly differently than Windows)
 50 |  - [x] Compatible with Linux (limited testing)
 51 |  - [x] Compatible with MacOS X (limited testing)
 52 |  - [ ] Compatible with iOS (No - not even for accessibility switch - This is not a “Made for iPhone” (MFI) compatible device)
 53 | 
 54 | ## Installation
 55 | - (Make sure your IDE of choice has support for ESP32 boards available. [Instructions can be found here.](https://github.com/espressif/arduino-esp32#installation-instructions))
 56 | - Download the zip version of this library from Github using either the "Code" -> "Download Zip" button or by cloning this repository to your Arduino library folder. If using the downloaded zip method, in the Arduino IDE go to "Sketch" -> "Include Library" -> "Add .ZIP Library..." and select the file you just downloaded.
 57 | - Repeat the previous step but for the [NimBLE library](https://github.com/h2zero/NimBLE-Arduino)
 58 | - Using the Arduino IDE Library manager, download the "Callback" library by Tom Stewart.
 59 | - In the Arduino IDE, you can now go to "File" -> "Examples" -> "ESP32-BLE-CompositeHID" and select an example to get started.
 60 | 
 61 | ## Example
 62 | 
 63 | ``` C++
 64 | #include <BleCompositeHID.h>
 65 | #include <KeyboardDevice.h>
 66 | #include <MouseDevice.h>
 67 | #include <GamepadDevice.h>
 68 | 
 69 | GamepadDevice gamepad;
 70 | KeyboardDevice keyboard;
 71 | MouseDevice mouse;
 72 | BleCompositeHID compositeHID("CompositeHID Keyboard Mouse Gamepad", "Mystfit", 100);
 73 | 
 74 | void setup() {
 75 |     Serial.begin(115200);
 76 | 
 77 |      // Add all devices to the composite HID device to manage them
 78 |     compositeHID.addDevice(&keyboard);
 79 |     compositeHID.addDevice(&mouse);
 80 |     compositeHID.addDevice(&gamepad);
 81 | 
 82 |     // Start the composite HID device to broadcast HID reports
 83 |     compositeHID.begin();
 84 | 
 85 |     delay(3000);
 86 | }
 87 | 
 88 | void loop() {
 89 |   if(compositeHID.isConnected()){
 90 | 
 91 |     // Test mouse by moving it in a circle
 92 |     int startTime = millis();
 93 |     int reportCount = 0;
 94 | 
 95 |     int8_t lastX = 0;
 96 |     int8_t lastY = 0;
 97 |     bool gamepadPressed = false;
 98 | 
 99 |     while(millis() - startTime < 8000){
100 |         reportCount++;
101 |         int8_t x = round(cos((float)millis() / 1000.0f) * 10.0f);
102 |         int8_t y = round(sin((float)millis() / 1000.0f) * 10.0f);
103 |         mouse->mouseMove(x, y);
104 | 
105 |         // Test keyboard presses
106 |         if(reportCount % 100 == 0){
107 |             keyboard->keyPress(KEY_A);
108 |             keyboard->keyRelease(KEY_A);
109 |         }
110 | 
111 |         // Test gamepad button presses
112 |         if(reportCount % 100 == 0){
113 |             gamepadPressed = !gamepadPressed;
114 |             if(gamepadPressed)
115 |                 gamepad->press(BUTTON_1);
116 |             else
117 |                 gamepad->release(BUTTON_1);
118 |         }
119 |         
120 |         delay(16);
121 |     }
122 |   }
123 | }
124 | 
125 | ```
126 | By default, reports are sent on every button press/release or axis/slider/hat/simulation movement, however this can be disabled, and then you manually call sendReport on each device instance as shown in the IndividualAxes.ino example.
127 | 
128 | VID and PID values can be set. See TestAll.ino for example.
129 | 
130 | There is also Bluetooth specific information that you can use (optional):
131 | 
132 | Instead of `BleCompositeHID bleCompositeHID;` you can do `BleCompositeHID bleCompositeHID("Bluetooth Device Name", "Bluetooth Device Manufacturer", 100);`.
133 | The third parameter is the initial battery level of your device.
134 | By default the battery level will be set to 100%, the device name will be `Composite HID` and the manufacturer will be `Espressif`.
135 | 
136 | The battery level can be set during operation by calling, for example, `bleCompositeHID.setBatteryLevel(80);`
137 | 
138 | ## Credits for ESP32-BLE-CompositeHID
139 | 
140 | Credit goes to lemmingDev for his work on [ESP32-BLE-Gamepad](https://github.com/lemmingDev/ESP32-BLE-Gamepad) which most of the gamepad portion of this library was based upon. 
141 | 
142 | USB HID codes for keyboards created by MightyPork, 2016 (see KeyboardHIDCodes.h)
143 | 
144 | ## Credits for ESP32-BLE-Gamepad
145 | 
146 | Credits to [T-vK](https://github.com/T-vK) as this library is based on his ESP32-BLE-Mouse library (https://github.com/T-vK/ESP32-BLE-Mouse) that he provided.
147 | 
148 | Credits to [chegewara](https://github.com/chegewara) as the ESP32-BLE-Mouse library is based on [this piece of code](https://github.com/nkolban/esp32-snippets/issues/230#issuecomment-473135679) that he provided.
149 | 
150 | Credits to [wakwak-koba](https://github.com/wakwak-koba) for the NimBLE [code](https://github.com/wakwak-koba/ESP32-NimBLE-Gamepad) that he provided.
151 | 
152 | 
153 | You might also be interested in:
154 | - [ESP32-BLE-Mouse](https://github.com/T-vK/ESP32-BLE-Mouse)
155 | - [ESP32-BLE-Keyboard](https://github.com/T-vK/ESP32-BLE-Keyboard)
156 | 
157 | or the NimBLE versions at
158 | 
159 | - [ESP32-NimBLE-Mouse](https://github.com/wakwak-koba/ESP32-NimBLE-Mouse)
160 | - [ESP32-NimBLE-Keyboard](https://github.com/wakwak-koba/ESP32-NimBLE-Keyboard)
161 | - [ESP32-BLE-Gamepad](https://github.com/lemmingDev/ESP32-BLE-Gamepad)
162 | 


--------------------------------------------------------------------------------
/SafeQueue.hpp:
--------------------------------------------------------------------------------
  1 | // MIT License
  2 | 
  3 | // Copyright (c) 2020 Adam Kecskes
  4 | 
  5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
  6 | // of this software and associated documentation files (the "Software"), to deal
  7 | // in the Software without restriction, including without limitation the rights
  8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 | // copies of the Software, and to permit persons to whom the Software is
 10 | // furnished to do so, subject to the following conditions:
 11 | 
 12 | // The above copyright notice and this permission notice shall be included in all
 13 | // copies or substantial portions of the Software.
 14 | 
 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 21 | // SOFTWARE.
 22 | 
 23 | // Created by Adam Kecskes
 24 | // https://github.com/K-Adam/SafeQueue
 25 | 
 26 | #pragma once
 27 | 
 28 | #include <mutex>
 29 | #include <condition_variable>
 30 | 
 31 | #include <queue>
 32 | #include <utility>
 33 | 
 34 | template<class T>
 35 | class SafeQueue {
 36 | 
 37 | 	std::queue<T> q;
 38 | 
 39 | 	std::mutex mtx;
 40 | 	std::condition_variable cv;
 41 | 
 42 | 	std::condition_variable sync_wait;
 43 | 	bool finish_processing = false;
 44 | 	int sync_counter = 0;
 45 | 
 46 | 	void DecreaseSyncCounter() {
 47 | 		if (--sync_counter == 0) {
 48 | 			sync_wait.notify_one();
 49 | 		}
 50 | 	}
 51 | 
 52 | public:
 53 | 
 54 | 	typedef typename std::queue<T>::size_type size_type;
 55 | 
 56 | 	SafeQueue() {}
 57 | 
 58 | 	~SafeQueue() {
 59 | 		Finish();
 60 | 	}
 61 | 
 62 | 	void Produce(T&& item) {
 63 | 
 64 | 		std::lock_guard<std::mutex> lock(mtx);
 65 | 
 66 | 		q.push(std::move(item));
 67 | 		cv.notify_one();
 68 | 
 69 | 	}
 70 | 
 71 | 	size_type Size() {
 72 | 
 73 | 		std::lock_guard<std::mutex> lock(mtx);
 74 | 
 75 | 		return q.size();
 76 | 
 77 | 	}
 78 | 
 79 | 	[[nodiscard]]
 80 | 	bool Consume(T& item) {
 81 | 
 82 | 		std::lock_guard<std::mutex> lock(mtx);
 83 | 
 84 | 		if (q.empty()) {
 85 | 			return false;
 86 | 		}
 87 | 
 88 | 		item = std::move(q.front());
 89 | 		q.pop();
 90 | 
 91 | 		return true;
 92 | 
 93 | 	}
 94 | 
 95 | 	[[nodiscard]]
 96 | 	bool ConsumeSync(T& item) {
 97 | 
 98 | 		std::unique_lock<std::mutex> lock(mtx);
 99 | 
100 | 		sync_counter++;
101 | 
102 | 		cv.wait(lock, [&] {
103 | 			return !q.empty() || finish_processing;
104 | 		});
105 | 
106 | 		if (q.empty()) {
107 | 			DecreaseSyncCounter();
108 | 			return false;
109 | 		}
110 | 
111 | 		item = std::move(q.front());
112 | 		q.pop();
113 | 
114 | 		DecreaseSyncCounter();
115 | 		return true;
116 | 
117 | 	}
118 | 
119 | 	void Finish() {
120 | 
121 | 		std::unique_lock<std::mutex> lock(mtx);
122 | 
123 | 		finish_processing = true;
124 | 		cv.notify_all();
125 | 
126 | 		sync_wait.wait(lock, [&]() {
127 | 			return sync_counter == 0;
128 | 		});
129 | 
130 | 		finish_processing = false;
131 | 
132 | 	}
133 | 
134 | };
135 | 


--------------------------------------------------------------------------------
/XboxGamepadConfiguration.h:
--------------------------------------------------------------------------------
 1 | #ifndef XBOX_GAMEPAD_CONFIGURATION_H
 2 | #define XBOX_GAMEPAD_CONFIGURATION_H
 3 | 
 4 | #include "XboxDescriptors.h"
 5 | #include "BaseCompositeDevice.h"
 6 | 
 7 | class XboxGamepadDeviceConfiguration : public BaseCompositeDeviceConfiguration {
 8 | public:
 9 |     XboxGamepadDeviceConfiguration(uint8_t reportId = XBOX_INPUT_REPORT_ID);
10 |     virtual uint8_t getDeviceReportSize() const override { return 0; }
11 |     virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override { 
12 |         return -1;
13 |     }
14 | };
15 | 
16 | 
17 | class XboxOneSControllerDeviceConfiguration : public XboxGamepadDeviceConfiguration {
18 | public:
19 |     virtual const char* getDeviceName() const { return "XboxOneS"; }
20 |     virtual BLEHostConfiguration getIdealHostConfiguration() const override;
21 |     virtual uint8_t getDeviceReportSize() const override;
22 |     virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override;
23 | };
24 | 
25 | 
26 | class XboxSeriesXControllerDeviceConfiguration : public XboxGamepadDeviceConfiguration {
27 | public:
28 |     virtual const char* getDeviceName() const { return "XboxSeriesX"; }
29 |     virtual BLEHostConfiguration getIdealHostConfiguration() const override;
30 |     virtual uint8_t getDeviceReportSize() const override;
31 |     virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override;
32 | };
33 | 
34 | #endif // XBOX_GAMEPAD_CONFIGURATION_H
35 | 


--------------------------------------------------------------------------------
/XboxGamepadConfigurations.cpp:
--------------------------------------------------------------------------------
  1 | #include "XboxGamepadConfiguration.h"
  2 | #include "XboxGamepadDevice.h"
  3 | 
  4 | 
  5 | // XboxGamepadDeviceConfiguration methods
  6 | XboxGamepadDeviceConfiguration::XboxGamepadDeviceConfiguration(uint8_t reportId) : 
  7 |     BaseCompositeDeviceConfiguration(reportId)
  8 | {
  9 | }
 10 | 
 11 | BLEHostConfiguration XboxOneSControllerDeviceConfiguration::getIdealHostConfiguration() const {
 12 |     // Fake a xbox controller
 13 |     BLEHostConfiguration config;
 14 | 
 15 |     // Vendor: Microsoft
 16 |     config.setVidSource(VENDOR_USB_SOURCE);
 17 |     config.setVid(XBOX_VENDOR_ID); 
 18 |     
 19 |     // Product: Xbox One Wireless Controller - Model 1708 pre 2021 firmware
 20 |     // Specifically picked since it provides rumble support on linux kernels earlier than 6.5
 21 |     config.setPid(XBOX_1708_PRODUCT_ID); 
 22 |     config.setGuidVersion(XBOX_1708_BCD_DEVICE_ID);
 23 |     config.setSerialNumber(XBOX_1708_SERIAL);
 24 | 
 25 |     return config;
 26 | }
 27 | 
 28 | uint8_t XboxOneSControllerDeviceConfiguration::getDeviceReportSize() const {
 29 |     // Return the size of the device report
 30 |     
 31 |     // Input
 32 |     // 2 * 16bit (2 bytes) for X and Y inclusive                = 4 bytes
 33 |     // 2 * 16bit (2 bytes) for Z and Rz inclusive               = 4 bytes
 34 |     // 1 * 10bit for brake + 6bit padding (2 bytes)             = 2 bytes
 35 |     // 1 * 10bit for accelerator + 6bit padding (2 bytes)       = 2 bytes
 36 |     // 1 * 4bit for hat switch + 4bit padding (1 byte)          = 1 byte
 37 |     // 15 * 1bit for buttons + 1bit padding (2 bytes)           = 2 bytes
 38 |     // 1 * 1bit for menu/select button + 7bit padding (1 byte)       = 1 byte
 39 | 
 40 |     // Additional input?
 41 |     // 1 * 1bit + 7bit padding (1 byte)                         = 1 byte
 42 | 
 43 |     // TODO: Split output size into seperate function
 44 |     // Output 
 45 |     // 1 * 4bit for DC Enable Actuators + 4bit padding (1 byte) = 1 byte
 46 |     // 4 * 8bit for Magnitude (4 bytes)                         = 4 bytes
 47 |     // 3 * 8bit for Duration, Start Delay, Loop Count (3 bytes) = 3 bytes
 48 | 
 49 |     return sizeof(XboxGamepadInputReportData); //16
 50 | }
 51 | 
 52 | size_t XboxOneSControllerDeviceConfiguration::makeDeviceReport(uint8_t* buffer, size_t bufferSize) const {
 53 |     size_t hidDescriptorSize = sizeof(XboxOneS_1708_HIDDescriptor);
 54 |     if(hidDescriptorSize < bufferSize){
 55 |         memcpy(buffer, XboxOneS_1708_HIDDescriptor, hidDescriptorSize);
 56 |     } else {
 57 |         return -1;
 58 |     }
 59 |     
 60 |     return hidDescriptorSize;
 61 | }
 62 | 
 63 | 
 64 | // -------------------------------------
 65 | 
 66 | 
 67 | BLEHostConfiguration XboxSeriesXControllerDeviceConfiguration::getIdealHostConfiguration() const {
 68 |     // Fake a xbox controller
 69 |     BLEHostConfiguration config;
 70 | 
 71 |     // Vendor: Microsoft
 72 |     config.setVidSource(VENDOR_USB_SOURCE);
 73 |     config.setVid(XBOX_VENDOR_ID); 
 74 |     
 75 |     // Product: Xbox Series X Wireless Controller - Model 1944 pre 2021 firmware
 76 |     // Only compatible on linux kernels >= 6.5
 77 |     config.setPid(XBOX_1914_PRODUCT_ID); 
 78 |     config.setGuidVersion(XBOX_1914_BCD_DEVICE_ID);
 79 |     config.setSerialNumber(XBOX_1914_SERIAL);
 80 | 
 81 |     return config;
 82 | }
 83 | 
 84 | uint8_t XboxSeriesXControllerDeviceConfiguration::getDeviceReportSize() const {
 85 |     // Return the size of the device report
 86 |     
 87 |     // Input
 88 |     // 2 * 16bit (2 bytes) for X and Y inclusive                = 4 bytes
 89 |     // 2 * 16bit (2 bytes) for Z and Rz inclusive               = 4 bytes
 90 |     // 1 * 10bit for brake + 6bit padding (2 bytes)             = 2 bytes
 91 |     // 1 * 10bit for accelerator + 6bit padding (2 bytes)       = 2 bytes
 92 |     // 1 * 4bit for hat switch + 4bit padding (1 byte)          = 1 byte
 93 |     // 15 * 1bit for buttons + 1bit padding (2 bytes)           = 2 bytes
 94 |     // 1 * 1bit for share button + 7bit padding (1 byte)       = 1 byte
 95 | 
 96 |     // Additional input?
 97 |     // 1 * 1bit + 7bit padding (1 byte)                         = 1 byte
 98 | 
 99 |     // TODO: Split output size into seperate function
100 |     // Output 
101 |     // 1 * 4bit for DC Enable Actuators + 4bit padding (1 byte) = 1 byte
102 |     // 4 * 8bit for Magnitude (4 bytes)                         = 4 bytes
103 |     // 3 * 8bit for Duration, Start Delay, Loop Count (3 bytes) = 3 bytes
104 | 
105 |     return sizeof(XboxGamepadInputReportData); //16;
106 | }
107 | 
108 | size_t XboxSeriesXControllerDeviceConfiguration::makeDeviceReport(uint8_t* buffer, size_t bufferSize) const {
109 |     size_t hidDescriptorSize = sizeof(XboxOneS_1914_HIDDescriptor);
110 |     if(hidDescriptorSize < bufferSize){
111 |         memcpy(buffer, XboxOneS_1914_HIDDescriptor, hidDescriptorSize);
112 |     } else {
113 |         return -1;
114 |     }
115 |     return hidDescriptorSize;
116 | }


--------------------------------------------------------------------------------
/XboxGamepadDevice.cpp:
--------------------------------------------------------------------------------
  1 | #include "XboxGamepadDevice.h"
  2 | #include "BleCompositeHID.h"
  3 | 
  4 | 
  5 | #if defined(CONFIG_ARDUHAL_ESP_LOG)
  6 | #include "esp32-hal-log.h"
  7 | #define LOG_TAG "XboxGamepadDevice"
  8 | #else
  9 | #include "esp_log.h"
 10 | static const char *LOG_TAG = "XboxGamepadDevice";
 11 | #endif
 12 | 
 13 | XboxGamepadCallbacks::XboxGamepadCallbacks(XboxGamepadDevice* device) : _device(device)
 14 | {
 15 | }
 16 | 
 17 | void XboxGamepadCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo)
 18 | {    
 19 |     // An example packet we might receive from XInput might look like 0x0300002500ff00ff
 20 |     XboxGamepadOutputReportData vibrationData = pCharacteristic->getValue<uint64_t>();
 21 |     
 22 |     ESP_LOGD(LOG_TAG, "XboxGamepadCallbacks::onWrite, Size: %d, DC enable: %d, magnitudeWeak: %d, magnitudeStrong: %d, duration: %d, start delay: %d, loop count: %d", 
 23 |         pCharacteristic->getValue().size(),
 24 |         vibrationData.dcEnableActuators, 
 25 |         vibrationData.weakMotorMagnitude, 
 26 |         vibrationData.strongMotorMagnitude, 
 27 |         vibrationData.duration, 
 28 |         vibrationData.startDelay, 
 29 |         vibrationData.loopCount
 30 |     );
 31 | 
 32 |     _device->onVibrate.fire(vibrationData);
 33 | }
 34 | 
 35 | void XboxGamepadCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo)
 36 | {
 37 |     ESP_LOGD(LOG_TAG, "XboxGamepadCallbacks::onRead");
 38 | }
 39 | 
 40 | void XboxGamepadCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue)
 41 | {
 42 |     ESP_LOGD(LOG_TAG, "XboxGamepadCallbacks::onSubscribe");
 43 | }
 44 | 
 45 | void XboxGamepadCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code)
 46 | {
 47 |     ESP_LOGD(LOG_TAG, "XboxGamepadCallbacks::onStatus, code: %d", code);
 48 | }
 49 | 
 50 | XboxGamepadDevice::XboxGamepadDevice() :
 51 |     _config(new XboxOneSControllerDeviceConfiguration()),
 52 |     _extra_input(nullptr),
 53 |     _callbacks(nullptr)
 54 | {
 55 | }
 56 | 
 57 | // XboxGamepadDevice methods
 58 | XboxGamepadDevice::XboxGamepadDevice(XboxGamepadDeviceConfiguration* config) :
 59 |     _config(config),
 60 |     _extra_input(nullptr),
 61 |     _callbacks(nullptr)
 62 | {
 63 | }
 64 | 
 65 | XboxGamepadDevice::~XboxGamepadDevice() {
 66 |     if (getOutput() && _callbacks){
 67 |         getOutput()->setCallbacks(nullptr);
 68 |         delete _callbacks;
 69 |         _callbacks = nullptr;
 70 |     }
 71 | 
 72 |     if(_extra_input){
 73 |         delete _extra_input;
 74 |         _extra_input = nullptr;
 75 |     }
 76 | 
 77 |     if(_config){
 78 |         delete _config;
 79 |         _config = nullptr;
 80 |     }
 81 | }
 82 | 
 83 | void XboxGamepadDevice::init(NimBLEHIDDevice* hid) {
 84 |     /// Create input characteristic to send events to the computer
 85 |     auto input = hid->inputReport(XBOX_INPUT_REPORT_ID);
 86 |     //_extra_input = hid->inputReport(XBOX_EXTRA_INPUT_REPORT_ID);
 87 | 
 88 |     // Create output characteristic to handle events coming from the computer
 89 |     auto output = hid->outputReport(XBOX_OUTPUT_REPORT_ID);
 90 |     _callbacks = new XboxGamepadCallbacks(this);
 91 |     output->setCallbacks(_callbacks);
 92 | 
 93 |     setCharacteristics(input, output);
 94 | }
 95 | 
 96 | const BaseCompositeDeviceConfiguration* XboxGamepadDevice::getDeviceConfig() const {
 97 |     // Return the device configuration
 98 |     return _config;
 99 | }
100 | 
101 | void XboxGamepadDevice::resetInputs() {
102 |     std::lock_guard<std::mutex> lock(_mutex);
103 |     memset(&_inputReport, 0, sizeof(XboxGamepadInputReportData));
104 | }
105 | 
106 | void XboxGamepadDevice::press(uint16_t button) {
107 |     // Avoid double presses
108 |     if (!isPressed(button))
109 |     {
110 |         {
111 |             std::lock_guard<std::mutex> lock(_mutex);
112 |             _inputReport.buttons |= button;
113 |             ESP_LOGD(LOG_TAG, "XboxGamepadDevice::press, button: %d", button);
114 |         }
115 | 
116 |         if (_config->getAutoReport())
117 |         {
118 |             sendGamepadReport();
119 |         }
120 |     }
121 | }
122 | 
123 | void XboxGamepadDevice::release(uint16_t button) {
124 |     // Avoid double presses
125 |     if (isPressed(button))
126 |     {   
127 |         {
128 |             std::lock_guard<std::mutex> lock(_mutex);
129 |             _inputReport.buttons ^= button;
130 |             ESP_LOGD(LOG_TAG, "XboxGamepadDevice::release, button: %d", button);
131 |         }
132 | 
133 |         if (_config->getAutoReport())
134 |         {
135 |             sendGamepadReport();
136 |         }
137 |     }
138 | }
139 | 
140 | bool XboxGamepadDevice::isPressed(uint16_t button) {
141 |     std::lock_guard<std::mutex> lock(_mutex);
142 |     return (bool)((_inputReport.buttons & button) == button);
143 | }
144 | 
145 | void XboxGamepadDevice::setLeftThumb(int16_t x, int16_t y) {
146 |     x = constrain(x, XBOX_STICK_MIN, XBOX_STICK_MAX);
147 |     y = constrain(y, XBOX_STICK_MIN, XBOX_STICK_MAX);
148 | 
149 |     if(_inputReport.x != x || _inputReport.y != y){
150 |         {
151 |             std::lock_guard<std::mutex> lock(_mutex);
152 |             _inputReport.x = (uint16_t)(x + 0x8000);
153 |             _inputReport.y = (uint16_t)(y + 0x8000);
154 |         }
155 | 
156 |         if (_config->getAutoReport())
157 |         {
158 |             sendGamepadReport();
159 |         }
160 |     }
161 | }
162 | 
163 | void XboxGamepadDevice::setRightThumb(int16_t z, int16_t rZ) {
164 |     z = constrain(z, XBOX_STICK_MIN, XBOX_STICK_MAX);
165 |     rZ = constrain(rZ, XBOX_STICK_MIN, XBOX_STICK_MAX);
166 | 
167 |     if(_inputReport.z != z || _inputReport.rz != rZ){
168 |         {
169 |             std::lock_guard<std::mutex> lock(_mutex);
170 |             _inputReport.z = (uint16_t)(z + 0x8000);
171 |             _inputReport.rz = (uint16_t)(rZ+ 0x8000);
172 |         }
173 | 
174 |         if (_config->getAutoReport())
175 |         {
176 |             sendGamepadReport();
177 |         }
178 |     }
179 | }
180 | 
181 | void XboxGamepadDevice::setLeftTrigger(uint16_t value) {
182 |     value = constrain(value, XBOX_TRIGGER_MIN, XBOX_TRIGGER_MAX);
183 | 
184 |     if (_inputReport.brake != value) {
185 |         {
186 |             std::lock_guard<std::mutex> lock(_mutex);
187 |             _inputReport.brake = value;
188 |         }
189 | 
190 |         if (_config->getAutoReport()) {
191 |             sendGamepadReport();
192 |         }
193 |     }
194 | }
195 | 
196 | void XboxGamepadDevice::setRightTrigger(uint16_t value) {
197 |     value = constrain(value, XBOX_TRIGGER_MIN, XBOX_TRIGGER_MAX);
198 | 
199 |     if (_inputReport.accelerator != value) {
200 |         {
201 |             std::lock_guard<std::mutex> lock(_mutex);
202 |             _inputReport.accelerator = value;
203 |         }
204 | 
205 |         if (_config->getAutoReport()) {
206 |             sendGamepadReport();
207 |         }
208 |     }
209 | }
210 | 
211 | void XboxGamepadDevice::setTriggers(uint16_t left, uint16_t right) {
212 |     left = constrain(left, XBOX_TRIGGER_MIN, XBOX_TRIGGER_MAX);
213 |     right = constrain(right, XBOX_TRIGGER_MIN, XBOX_TRIGGER_MAX);
214 | 
215 |     if (_inputReport.brake != left || _inputReport.accelerator != right) {
216 |         {
217 |             std::lock_guard<std::mutex> lock(_mutex);
218 |             _inputReport.brake = left;
219 |             _inputReport.accelerator = right;
220 |         }
221 |         if (_config->getAutoReport()) {
222 |             sendGamepadReport();
223 |         }
224 |     }
225 | }
226 | 
227 | void XboxGamepadDevice::pressDPadDirection(uint8_t direction) {
228 | 
229 |     // Avoid double presses
230 |     if (!isDPadPressed(direction))
231 |     {
232 |         ESP_LOGD(LOG_TAG, "Pressing dpad direction %s", dPadDirectionName(direction).c_str());
233 |         {
234 |             std::lock_guard<std::mutex> lock(_mutex);
235 |             _inputReport.hat = direction;
236 |         }
237 | 
238 |         if (_config->getAutoReport())
239 |         {
240 |             sendGamepadReport();
241 |         }
242 |     }
243 | }
244 | 
245 | void XboxGamepadDevice::pressDPadDirectionFlag(XboxDpadFlags direction) {
246 |     // Filter opposite button presses
247 |     if((direction & (XboxDpadFlags::NORTH | XboxDpadFlags::SOUTH)) == (XboxDpadFlags::NORTH | XboxDpadFlags::SOUTH)){
248 |         ESP_LOGD(LOG_TAG, "Filtering opposite button presses - up down");
249 |         direction = (XboxDpadFlags)(direction ^ (uint8_t)(XboxDpadFlags::NORTH | XboxDpadFlags::SOUTH));
250 |     }
251 |     if((direction & (XboxDpadFlags::EAST | XboxDpadFlags::WEST)) == (XboxDpadFlags::EAST | XboxDpadFlags::WEST)){
252 |         ESP_LOGD(LOG_TAG, "Filtering opposite button presses - left right");
253 |         direction = (XboxDpadFlags)(direction ^ (uint8_t)(XboxDpadFlags::EAST | XboxDpadFlags::WEST));
254 |     }
255 | 
256 |     pressDPadDirection(dPadDirectionToValue(direction));
257 | }
258 | 
259 | void XboxGamepadDevice::releaseDPad() {
260 |     pressDPadDirection(XBOX_BUTTON_DPAD_NONE);
261 | }
262 | 
263 | bool XboxGamepadDevice::isDPadPressed(uint8_t direction) {
264 |     std::lock_guard<std::mutex> lock(_mutex);
265 |     // Serial.print("Internal hat value:");
266 |     // Serial.println(_inputReport.hat, HEX);
267 |     return _inputReport.hat == direction;
268 |     //return (bool)((_inputReport.hat & direction) == direction);
269 | }
270 | 
271 | bool XboxGamepadDevice::isDPadPressedFlag(XboxDpadFlags direction) {
272 |     std::lock_guard<std::mutex> lock(_mutex);
273 | 
274 |     if(direction == XboxDpadFlags::NORTH){
275 |         return _inputReport.hat == XBOX_BUTTON_DPAD_NORTH;
276 |     } else if(direction == (XboxDpadFlags::NORTH & XboxDpadFlags::EAST)){
277 |         return _inputReport.hat == XBOX_BUTTON_DPAD_NORTHEAST;
278 |     } else if(direction == XboxDpadFlags::EAST){
279 |         return _inputReport.hat == XBOX_BUTTON_DPAD_EAST;
280 |     } else if(direction == (XboxDpadFlags::SOUTH & XboxDpadFlags::EAST)){
281 |         return _inputReport.hat == XBOX_BUTTON_DPAD_SOUTHEAST;
282 |     } else if(direction == XboxDpadFlags::SOUTH){
283 |         return _inputReport.hat == XBOX_BUTTON_DPAD_SOUTH;
284 |     } else if(direction == (XboxDpadFlags::SOUTH & XboxDpadFlags::WEST)){
285 |         return _inputReport.hat == XBOX_BUTTON_DPAD_SOUTHWEST;
286 |     } else if(direction == XboxDpadFlags::WEST){
287 |         return _inputReport.hat == XBOX_BUTTON_DPAD_WEST;
288 |     } else if(direction == (XboxDpadFlags::NORTH & XboxDpadFlags::WEST)){
289 |         return _inputReport.hat == XBOX_BUTTON_DPAD_NORTHWEST;
290 |     }
291 |     return false;
292 | }
293 | 
294 | 
295 | void XboxGamepadDevice::pressShare() {
296 |     // Avoid double presses
297 |     if (!(_inputReport.share & XBOX_BUTTON_SHARE))
298 |     {
299 |         {
300 |             std::lock_guard<std::mutex> lock(_mutex);
301 |             _inputReport.share |= XBOX_BUTTON_SHARE;
302 |         }
303 | 
304 |         if (_config->getAutoReport())
305 |         {
306 |             sendGamepadReport();
307 |         }
308 |     }
309 | }
310 | 
311 | void XboxGamepadDevice::releaseShare() {
312 |     if (_inputReport.share & XBOX_BUTTON_SHARE)
313 |     {
314 |         {
315 |             std::lock_guard<std::mutex> lock(_mutex);
316 |             _inputReport.share ^= XBOX_BUTTON_SHARE;
317 |         }
318 | 
319 |         if (_config->getAutoReport())
320 |         {
321 |             sendGamepadReport();
322 |         }
323 |     }
324 | }
325 | 
326 | void XboxGamepadDevice::sendGamepadReport(bool defer) {
327 |     if(defer || _config->getAutoDefer()){
328 |         queueDeferredReport(std::bind(&XboxGamepadDevice::sendGamepadReportImpl, this));
329 |     } else {
330 |         sendGamepadReportImpl();
331 |     }
332 | }
333 | 
334 | void XboxGamepadDevice::sendGamepadReportImpl(){
335 |     auto input = getInput();
336 |     auto parentDevice = this->getParent();
337 | 
338 |     if (!input || !parentDevice)
339 |         return;
340 | 
341 |     if(!parentDevice->isConnected())
342 |         return;
343 | 
344 |     {
345 |         std::lock_guard<std::mutex> lock(_mutex);
346 |         size_t packedSize = sizeof(_inputReport);
347 |         ESP_LOGD(LOG_TAG, "Sending gamepad report, size: %d", packedSize);
348 |         input->setValue((uint8_t*)&_inputReport, packedSize);
349 |     }
350 |     input->notify();
351 | }
352 | 


--------------------------------------------------------------------------------
/XboxGamepadDevice.h:
--------------------------------------------------------------------------------
  1 | #ifndef XBOX_GAMEPAD_DEVICE_H
  2 | #define XBOX_GAMEPAD_DEVICE_H
  3 | 
  4 | #include <NimBLECharacteristic.h>
  5 | #include <Callback.h>
  6 | #include <mutex>
  7 | 
  8 | #include "BLEHostConfiguration.h"
  9 | #include "BaseCompositeDevice.h"
 10 | #include "GamepadDevice.h"
 11 | #include "XboxDescriptors.h"
 12 | #include "XboxGamepadConfiguration.h"
 13 | 
 14 | // Button bitmasks
 15 | #define XBOX_BUTTON_A 0x01
 16 | #define XBOX_BUTTON_B 0x02
 17 | // UNUSED - 0x04
 18 | #define XBOX_BUTTON_X 0x08 
 19 | #define XBOX_BUTTON_Y 0x10
 20 | // UNUSED - 0x20
 21 | #define XBOX_BUTTON_LB 0x40
 22 | #define XBOX_BUTTON_RB 0x80
 23 | // UNUSED - 0x100
 24 | // UNUSED - 0x200
 25 | // UNUSED - 0x400
 26 | #define XBOX_BUTTON_SELECT 0x400
 27 | #define XBOX_BUTTON_START 0x800
 28 | #define XBOX_BUTTON_HOME 0x1000
 29 | #define XBOX_BUTTON_LS 0x2000
 30 | #define XBOX_BUTTON_RS 0x4000
 31 | 
 32 | // Select bitmask
 33 | // The share button lives in its own byte at the end of the input report
 34 | #define XBOX_BUTTON_SHARE 0x01
 35 | 
 36 | // Dpad values
 37 | #define XBOX_BUTTON_DPAD_NONE 0x00
 38 | #define XBOX_BUTTON_DPAD_NORTH 0x01
 39 | #define XBOX_BUTTON_DPAD_NORTHEAST 0x02
 40 | #define XBOX_BUTTON_DPAD_EAST 0x03
 41 | #define XBOX_BUTTON_DPAD_SOUTHEAST 0x04
 42 | #define XBOX_BUTTON_DPAD_SOUTH 0x05
 43 | #define XBOX_BUTTON_DPAD_SOUTHWEST 0x06
 44 | #define XBOX_BUTTON_DPAD_WEST 0x07
 45 | #define XBOX_BUTTON_DPAD_NORTHWEST 0x08
 46 | 
 47 | // Dpad bitflags
 48 | enum XboxDpadFlags : uint8_t {
 49 |     NONE = 0x00,
 50 |     NORTH = 0x01,
 51 |     EAST = 0x02,
 52 |     SOUTH = 0x04,
 53 |     WEST = 0x08
 54 | };
 55 | 
 56 | // Trigger range
 57 | #define XBOX_TRIGGER_MIN 0
 58 | #define XBOX_TRIGGER_MAX 1023
 59 | 
 60 | // Thumbstick range
 61 | #define XBOX_STICK_MIN -32768
 62 | #define XBOX_STICK_MAX 32767
 63 | 
 64 | 
 65 | // Forwards
 66 | class XboxGamepadDevice;
 67 | 
 68 | 
 69 | class XboxGamepadCallbacks : public NimBLECharacteristicCallbacks
 70 | {
 71 | public:
 72 |     XboxGamepadCallbacks(XboxGamepadDevice* device);
 73 | 
 74 |     void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
 75 |     void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
 76 |     void onStatus(NimBLECharacteristic* pCharacteristic, int code) override;
 77 |     void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override;
 78 | 
 79 | private:
 80 |     XboxGamepadDevice* _device;
 81 | };
 82 | 
 83 | struct XboxGamepadOutputReportData {
 84 |     uint8_t dcEnableActuators = 0x00;   // 4bits for DC Enable Actuators, 4bits padding
 85 |     uint8_t leftTriggerMagnitude = 0;
 86 |     uint8_t rightTriggerMagnitude = 0; 
 87 |     uint8_t weakMotorMagnitude = 0;
 88 |     uint8_t strongMotorMagnitude = 0; 
 89 |     uint8_t duration = 0;               // UNUSED
 90 |     uint8_t startDelay = 0;             // UNUSED
 91 |     uint8_t loopCount = 0;              // UNUSED
 92 | 
 93 |     constexpr XboxGamepadOutputReportData(uint64_t value = 0) noexcept : 
 94 |         dcEnableActuators((value & 0xFF)),
 95 |         leftTriggerMagnitude((value >> 8) & 0xFF),
 96 |         rightTriggerMagnitude((value >> 16) & 0xFF),
 97 |         weakMotorMagnitude((value >> 24) & 0xFF),
 98 |         strongMotorMagnitude((value >> 32) & 0xFF),
 99 |         duration((value >> 40) & 0xFF),
100 |         startDelay((value >> 48) & 0xFF),
101 |         loopCount((value >> 56) & 0xFF)
102 |     {}
103 | };
104 | 
105 | #pragma pack(push, 1)
106 | struct XboxGamepadInputReportData {
107 |     uint16_t x = 0;             // Left joystick X
108 |     uint16_t y = 0;             // Left joystick Y
109 |     uint16_t z = 0;             // Right jostick X
110 |     uint16_t rz = 0;            // Right joystick Y
111 |     uint16_t brake = 0;         // 10 bits for brake (left trigger) + 6 bit padding (2 bytes)
112 |     uint16_t accelerator = 0;   // 10 bits for accelerator (right trigger) + 6bit padding
113 |     uint8_t hat = 0x00;         // 4bits for hat switch (Dpad) + 4 bit padding (1 byte) 
114 |     uint16_t buttons = 0x00;    // 15 * 1bit for buttons + 1 bit padding (2 bytes)
115 |     uint8_t share = 0x00;      // 1 bits for share/menu button + 7 bit padding (1 byte)
116 | };
117 | #pragma pack(pop)
118 | 
119 | 
120 | static uint8_t dPadDirectionToValue(XboxDpadFlags direction){
121 |     if(direction == XboxDpadFlags::NORTH)
122 |         return XBOX_BUTTON_DPAD_NORTH;
123 |     else if(direction == (XboxDpadFlags::EAST | XboxDpadFlags::NORTH))
124 |         return XBOX_BUTTON_DPAD_NORTHEAST;
125 |     else if(direction == XboxDpadFlags::EAST)
126 |         return XBOX_BUTTON_DPAD_EAST;
127 |     else if(direction == (XboxDpadFlags::EAST | XboxDpadFlags::SOUTH))
128 |         return XBOX_BUTTON_DPAD_SOUTHEAST;
129 |     else if(direction == XboxDpadFlags::SOUTH)
130 |         return XBOX_BUTTON_DPAD_SOUTH;
131 |     else if(direction == (XboxDpadFlags::WEST | XboxDpadFlags::SOUTH))
132 |         return XBOX_BUTTON_DPAD_SOUTHWEST;
133 |     else if(direction == XboxDpadFlags::WEST)
134 |         return XBOX_BUTTON_DPAD_WEST;
135 |     else if(direction == (XboxDpadFlags::WEST | XboxDpadFlags::NORTH))
136 |         return XBOX_BUTTON_DPAD_NORTHWEST;
137 |     
138 |     return XBOX_BUTTON_DPAD_NONE;
139 | }
140 | 
141 | static String dPadDirectionName(uint8_t direction){
142 |     if(direction == XBOX_BUTTON_DPAD_NORTH)
143 |         return "NORTH";
144 |     else if(direction == XBOX_BUTTON_DPAD_NORTHEAST)
145 |         return "NORTHEAST";
146 |     else if(direction == XBOX_BUTTON_DPAD_EAST)
147 |         return "EAST";
148 |     else if(direction == XBOX_BUTTON_DPAD_SOUTHEAST)
149 |         return "SOUTHEAST";
150 |     else if(direction == XBOX_BUTTON_DPAD_SOUTH)
151 |         return "SOUTH";
152 |     else if(direction == XBOX_BUTTON_DPAD_SOUTHWEST)
153 |         return "SOUTHWEST";
154 |     else if(direction == XBOX_BUTTON_DPAD_WEST)
155 |         return "WEST";
156 |     else if(direction == XBOX_BUTTON_DPAD_NORTHWEST)
157 |         return "NORTHWEST";
158 |     return "NONE";
159 | }
160 | 
161 | 
162 | class XboxGamepadDevice : public BaseCompositeDevice {
163 | public:
164 |     XboxGamepadDevice();
165 |     XboxGamepadDevice(XboxGamepadDeviceConfiguration* config);
166 |     ~XboxGamepadDevice();
167 | 
168 |     void init(NimBLEHIDDevice* hid) override;
169 |     const BaseCompositeDeviceConfiguration* getDeviceConfig() const override;
170 | 
171 |     Signal<XboxGamepadOutputReportData> onVibrate;
172 | 
173 |     // Input Controls
174 |     void resetInputs();
175 |     void press(uint16_t button = XBOX_BUTTON_A);    
176 |     void release(uint16_t button = XBOX_BUTTON_A); 
177 |     bool isPressed(uint16_t button = XBOX_BUTTON_A);
178 |     void setLeftThumb(int16_t x = 0, int16_t y = 0);
179 |     void setRightThumb(int16_t z = 0, int16_t rZ = 0);
180 |     void setLeftTrigger(uint16_t rX = 0);
181 |     void setRightTrigger(uint16_t rY = 0);
182 |     void setTriggers(uint16_t rX = 0, uint16_t rY = 0);
183 |     void pressDPadDirection(uint8_t direction = 0);
184 |     void pressDPadDirectionFlag(XboxDpadFlags direction = XboxDpadFlags::NONE);
185 |     void releaseDPad();
186 |     bool isDPadPressed(uint8_t direction = 0);
187 |     bool isDPadPressedFlag(XboxDpadFlags direction);
188 |     void pressShare();
189 |     void releaseShare();
190 |     
191 |     void sendGamepadReport(bool defer = false);
192 | 
193 | private:
194 |     void sendGamepadReportImpl();
195 | 
196 |     XboxGamepadInputReportData _inputReport;
197 | 
198 |     NimBLECharacteristic* _extra_input;
199 |     XboxGamepadCallbacks* _callbacks;
200 |     XboxGamepadDeviceConfiguration* _config;
201 | 
202 |     // Threading
203 |     std::mutex _mutex;
204 | };
205 | 
206 | #endif // XBOX_GAMEPAD_DEVICE_H
207 | 


--------------------------------------------------------------------------------
/examples/GamepadExamples/CharacteristicsConfiguration/CharacteristicsConfiguration.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Sets BLE characteristic options
 3 |  * Also shows how to set a custom MAC address
 4 |  * Use BLE Scanner etc on Android to see them
 5 |  */
 6 | 
 7 | #include <Arduino.h>
 8 | #include <GamepadDevice.h>
 9 | #include <BleCompositeHID.h>
10 | 
11 | BleCompositeHID compositeHID("Custom Contoller Name", "lemmingDev", 100);   // Create a BleCompositeHID object to manage all of the devices
12 | 
13 | GamepadDevice* bleGamepad; // Set custom device name, manufacturer and initial battery level
14 | BLEHostConfiguration bleHostConfig;   // Create a BleGamepadConfiguration object to store all of the options
15 | 
16 | // Use the procedure below to set a custom Bluetooth MAC address
17 | // Compiler adds 0x02 to the last value of board's base MAC address to get the BT MAC address, so take 0x02 away from the value you actually want when setting
18 | // I've noticed the first number is a little picky and if set incorrectly don't work and will default to the board's embedded address
19 | // 0xAA definately works, so use that, or experiment
20 | uint8_t newMACAddress[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF - 0x02};
21 |     
22 | void setup()
23 | {
24 |     esp_base_mac_addr_set(&newMACAddress[0]); // Set new MAC address
25 | 
26 |     Serial.begin(115200);
27 |     Serial.println("Starting BLE work!");
28 | 
29 |     // Set up the gamepad specific configuration options
30 |     GamepadConfiguration gamepadConfig;
31 |     gamepadConfig.setAutoReport(false);
32 |     gamepadConfig.setControllerType(CONTROLLER_TYPE_GAMEPAD); // CONTROLLER_TYPE_JOYSTICK, CONTROLLER_TYPE_GAMEPAD (DEFAULT), CONTROLLER_TYPE_MULTI_AXIS
33 |     
34 |     // Some non-Windows operating systems and web based gamepad testers don't like min axis set below 0, so 0 is set by default
35 |     //bleGamepadConfig.setAxesMin(0x8001); // -32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
36 | 	gamepadConfig.setAxesMin(0x0000); // 0 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal    
37 |     gamepadConfig.setAxesMax(0x7FFF); // 32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal     
38 |     
39 |     // Set up gamepad device late so we can pass our new config
40 |     bleGamepad = new GamepadDevice(gamepadConfig);
41 |     
42 |     // Add gamepad to composite HID device
43 |     compositeHID.addDevice(bleGamepad); 
44 | 
45 |     // Set up the host configuration options for the top level composite HID device
46 |     bleHostConfig.setVid(0xe502);
47 |     bleHostConfig.setPid(0xabcd);
48 |     bleHostConfig.setModelNumber("1.0");
49 |     bleHostConfig.setSoftwareRevision("Software Rev 1");
50 |     bleHostConfig.setSerialNumber("9876543210");
51 |     bleHostConfig.setFirmwareRevision("2.0");
52 |     bleHostConfig.setHardwareRevision("1.7");
53 | 
54 |     // Start the composite HID device
55 |     compositeHID.begin(bleHostConfig); 
56 | }
57 | 
58 | void loop()
59 | {
60 |     if (compositeHID.isConnected())
61 |     {
62 | 
63 |     }
64 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/DrivingControllerTest/DrivingControllerTest.ino:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Driving controller test
  3 |  */
  4 | 
  5 | #include <Arduino.h>
  6 | #include <GamepadDevice.h>
  7 | #include <BleCompositeHID.h>
  8 | 
  9 | #define numOfButtons 10
 10 | #define numOfHatSwitches 0
 11 | #define enableX false
 12 | #define enableY false
 13 | #define enableZ false
 14 | #define enableRX false
 15 | #define enableRY false
 16 | #define enableRZ false
 17 | #define enableSlider1 false
 18 | #define enableSlider2 false
 19 | #define enableRudder false
 20 | #define enableThrottle false
 21 | #define enableAccelerator true
 22 | #define enableBrake true
 23 | #define enableSteering true
 24 | 
 25 | BleCompositeHID compositeHID("BLE Driving Controller", "lemmingDev", 100);
 26 | GamepadDevice* gamepad;
 27 | 
 28 | void setup()
 29 | {
 30 |     Serial.begin(115200);
 31 |     Serial.println("Starting BLE work!");
 32 | 
 33 |     // Setup controller with 10 buttons, accelerator, brake and steering
 34 |     GamepadConfiguration bleGamepadConfig;
 35 |     bleGamepadConfig.setAutoReport(false);
 36 |     bleGamepadConfig.setControllerType(CONTROLLER_TYPE_GAMEPAD); // CONTROLLER_TYPE_JOYSTICK, CONTROLLER_TYPE_GAMEPAD (DEFAULT), CONTROLLER_TYPE_MULTI_AXIS
 37 |     bleGamepadConfig.setButtonCount(numOfButtons);
 38 |     bleGamepadConfig.setWhichAxes(enableX, enableY, enableZ, enableRX, enableRY, enableRZ, enableSlider1, enableSlider2);      // Can also be done per-axis individually. All are true by default
 39 |     bleGamepadConfig.setWhichSimulationControls(enableRudder, enableThrottle, enableAccelerator, enableBrake, enableSteering); // Can also be done per-control individually. All are false by default
 40 |     bleGamepadConfig.setHatSwitchCount(numOfHatSwitches);                                                                      // 1 by default
 41 |     // Some non-Windows operating systems and web based gamepad testers don't like min axis set below 0, so 0 is set by default
 42 |     bleGamepadConfig.setAxesMin(0x8001); // -32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
 43 |     bleGamepadConfig.setAxesMax(0x7FFF); // 32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal 
 44 |     
 45 |     gamepad = new GamepadDevice(bleGamepadConfig);
 46 |     compositeHID.addDevice(gamepad);
 47 | 
 48 | 	compositeHID.begin();
 49 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
 50 | 
 51 |     // Set accelerator and brake to min
 52 |     gamepad->setAccelerator(-32767);
 53 |     gamepad->setBrake(-32767);
 54 | 
 55 |     // Set steering to center
 56 |     gamepad->setSteering(0);
 57 | }
 58 | 
 59 | void loop()
 60 | {
 61 |     if (compositeHID.isConnected())
 62 |     {
 63 |         Serial.println("Press all buttons one by one");
 64 |         for (int i = 1; i <= numOfButtons; i += 1)
 65 |         {
 66 |             gamepad->press(i);
 67 |             gamepad->sendGamepadReport();
 68 |             delay(100);
 69 |             gamepad->release(i);
 70 |             gamepad->sendGamepadReport();
 71 |             delay(25);
 72 |         }
 73 | 
 74 |         Serial.println("Move steering from center to max");
 75 |         for (int i = 0; i > -32767; i -= 256)
 76 |         {
 77 |             gamepad->setSteering(i);
 78 |             gamepad->sendGamepadReport();
 79 |             delay(10);
 80 |         }
 81 | 
 82 |         Serial.println("Move steering from min to max");
 83 |         for (int i = -32767; i < 32767; i += 256)
 84 |         {
 85 |             gamepad->setSteering(i);
 86 |             gamepad->sendGamepadReport();
 87 |             delay(10);
 88 |         }
 89 | 
 90 |         Serial.println("Move steering from max to center");
 91 |         for (int i = 32767; i > 0; i -= 256)
 92 |         {
 93 |             gamepad->setSteering(i);
 94 |             gamepad->sendGamepadReport();
 95 |             delay(10);
 96 |         }
 97 |         gamepad->setSteering(0);
 98 |         gamepad->sendGamepadReport();
 99 | 
100 |         Serial.println("Move accelerator from min to max");
101 |         // for(int i = 32767 ; i > -32767 ; i -= 256)    //Use this for loop setup instead if accelerator is reversed
102 |         for (int i = -32767; i < 32767; i += 256)
103 |         {
104 |             gamepad->setAccelerator(i);
105 |             gamepad->sendGamepadReport();
106 |             delay(10);
107 |         }
108 |         gamepad->setAccelerator(-32767);
109 |         gamepad->sendGamepadReport();
110 | 
111 |         Serial.println("Move brake from min to max");
112 |         for (int i = -32767; i < 32767; i += 256)
113 |         {
114 |             gamepad->setBrake(i);
115 |             gamepad->sendGamepadReport();
116 |             delay(10);
117 |         }
118 |         gamepad->setBrake(-32767);
119 |         gamepad->sendGamepadReport();
120 |     }
121 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/FlightControllerTest/FlightControllerTest.ino:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Flight controller test
  3 |  */
  4 | 
  5 | #include <Arduino.h>
  6 | #include <GamepadDevice.h>
  7 | #include <BleCompositeHID.h>
  8 | 
  9 | #define numOfButtons 32
 10 | #define numOfHatSwitches 0
 11 | #define enableX true
 12 | #define enableY true
 13 | #define enableZ false
 14 | #define enableRZ false
 15 | #define enableRX false
 16 | #define enableRY false
 17 | #define enableSlider1 false
 18 | #define enableSlider2 false
 19 | #define enableRudder true
 20 | #define enableThrottle true
 21 | #define enableAccelerator false
 22 | #define enableBrake false
 23 | #define enableSteering false
 24 | 
 25 | //BleGamepad bleGamepad("BLE Flight Controller", "lemmingDev", 100);
 26 | BleCompositeHID compositeHID("BLE Flight Controller", "lemmingDev", 100);
 27 | GamepadDevice* gamepad;
 28 | 
 29 | void setup()
 30 | {
 31 |     Serial.begin(115200);
 32 |     Serial.println("Starting BLE work!");
 33 | 
 34 |     // Setup controller with 10 buttons, accelerator, brake and steering
 35 |     GamepadConfiguration bleGamepadConfig;
 36 |     bleGamepadConfig.setAutoReport(false);
 37 |     bleGamepadConfig.setControllerType(CONTROLLER_TYPE_GAMEPAD); // CONTROLLER_TYPE_JOYSTICK, CONTROLLER_TYPE_GAMEPAD (DEFAULT), CONTROLLER_TYPE_MULTI_AXIS
 38 |     bleGamepadConfig.setButtonCount(numOfButtons);
 39 |     bleGamepadConfig.setWhichAxes(enableX, enableY, enableZ, enableRX, enableRY, enableRZ, enableSlider1, enableSlider2);      // Can also be done per-axis individually. All are true by default
 40 |     bleGamepadConfig.setWhichSimulationControls(enableRudder, enableThrottle, enableAccelerator, enableBrake, enableSteering); // Can also be done per-control individually. All are false by default
 41 |     bleGamepadConfig.setHatSwitchCount(numOfHatSwitches);                                                                      // 1 by default
 42 |     // Some non-Windows operating systems and web based gamepad testers don't like min axis set below 0, so 0 is set by default
 43 |     bleGamepadConfig.setAxesMin(0x8001); // -32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
 44 |     bleGamepadConfig.setAxesMax(0x7FFF); // 32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal 
 45 |     
 46 |     gamepad = new GamepadDevice(bleGamepadConfig);
 47 |     compositeHID.addDevice(gamepad);
 48 | 
 49 | 	compositeHID.begin();
 50 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
 51 | 
 52 |     // Set throttle to min
 53 |     gamepad->setThrottle(-32767);
 54 | 
 55 |     // Set x and y axes and rudder to center
 56 |     gamepad->setX(0);
 57 |     gamepad->setY(0);
 58 |     gamepad->setRudder(0);
 59 | }
 60 | 
 61 | void loop()
 62 | {
 63 |     if (compositeHID.isConnected())
 64 |     {
 65 |         Serial.println("Press all buttons one by one");
 66 |         for (int i = 1; i <= numOfButtons; i += 1)
 67 |         {
 68 |             gamepad->press(i);
 69 |             gamepad->sendGamepadReport();
 70 |             delay(100);
 71 |             gamepad->release(i);
 72 |             gamepad->sendGamepadReport();
 73 |             delay(25);
 74 |         }
 75 | 
 76 |         Serial.println("Press start and select");
 77 |         gamepad->pressStart();
 78 |         delay(100);
 79 |         gamepad->pressSelect();
 80 |         delay(100);
 81 |         gamepad->releaseStart();
 82 |         delay(100);
 83 |         gamepad->releaseSelect();
 84 | 
 85 |         Serial.println("Move x axis from center to max");
 86 |         for (int i = 0; i > -32767; i -= 256)
 87 |         {
 88 |             gamepad->setX(i);
 89 |             gamepad->sendGamepadReport();
 90 |             delay(10);
 91 |         }
 92 | 
 93 |         Serial.println("Move x axis from min to max");
 94 |         for (int i = -32767; i < 32767; i += 256)
 95 |         {
 96 |             gamepad->setX(i);
 97 |             gamepad->sendGamepadReport();
 98 |             delay(10);
 99 |         }
100 | 
101 |         Serial.println("Move x axis from max to center");
102 |         for (int i = 32767; i > 0; i -= 256)
103 |         {
104 |             gamepad->setX(i);
105 |             gamepad->sendGamepadReport();
106 |             delay(10);
107 |         }
108 |         gamepad->setX(0);
109 |         gamepad->sendGamepadReport();
110 | 
111 |         Serial.println("Move y axis from center to max");
112 |         for (int i = 0; i > -32767; i -= 256)
113 |         {
114 |             gamepad->setY(i);
115 |             gamepad->sendGamepadReport();
116 |             delay(10);
117 |         }
118 | 
119 |         Serial.println("Move y axis from min to max");
120 |         for (int i = -32767; i < 32767; i += 256)
121 |         {
122 |             gamepad->setY(i);
123 |             gamepad->sendGamepadReport();
124 |             delay(10);
125 |         }
126 | 
127 |         Serial.println("Move y axis from max to center");
128 |         for (int i = 32767; i > 0; i -= 256)
129 |         {
130 |             gamepad->setY(i);
131 |             gamepad->sendGamepadReport();
132 |             delay(10);
133 |         }
134 |         gamepad->setY(0);
135 |         gamepad->sendGamepadReport();
136 | 
137 |         Serial.println("Move rudder from min to max");
138 |         // for(int i = 32767 ; i > -32767 ; i -= 256)    //Use this for loop setup instead if rudder is reversed
139 |         for (int i = -32767; i < 32767; i += 256)
140 |         {
141 |             gamepad->setRudder(i);
142 |             gamepad->sendGamepadReport();
143 |             delay(10);
144 |         }
145 |         gamepad->setRudder(0);
146 |         gamepad->sendGamepadReport();
147 | 
148 |         Serial.println("Move throttle from min to max");
149 |         for (int i = -32767; i < 32767; i += 256)
150 |         {
151 |             gamepad->setThrottle(i);
152 |             gamepad->sendGamepadReport();
153 |             delay(10);
154 |         }
155 |         gamepad->setThrottle(-32767);
156 |         gamepad->sendGamepadReport();
157 |     }
158 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/Gamepad/Gamepad.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * This example turns the ESP32 into a Bluetooth LE gamepad that presses buttons and moves axis
 3 |  *
 4 |  * At the moment we are using the default settings, but they can be canged using a BleGamepadConfig instance as parameter for the begin function.
 5 |  *
 6 |  * Possible buttons are:
 7 |  * BUTTON_1 through to BUTTON_16
 8 |  * (16 buttons by default. Library can be configured to use up to 128)
 9 |  *
10 |  * Possible DPAD/HAT switch position values are:
11 |  * DPAD_CENTERED, DPAD_UP, DPAD_UP_RIGHT, DPAD_RIGHT, DPAD_DOWN_RIGHT, DPAD_DOWN, DPAD_DOWN_LEFT, DPAD_LEFT, DPAD_UP_LEFT
12 |  * (or HAT_CENTERED, HAT_UP etc)
13 |  *
14 |  * gamepad->setAxes sets all axes at once. There are a few:
15 |  * (x axis, y axis, z axis, rx axis, ry axis, rz axis, slider 1, slider 2)
16 |  *
17 |  * Library can also be configured to support up to 5 simulation controls
18 |  * (rudder, throttle, accelerator, brake, steering), but they are not enabled by default.
19 |  *
20 |  * Library can also be configured to support different function buttons
21 |  * (start, select, menu, home, back, volume increase, volume decrease, volume mute)
22 |  * start and select are enabled by default
23 |  */
24 | 
25 | #include <Arduino.h>
26 | #include <GamepadDevice.h>
27 | #include <BleCompositeHID.h>
28 | 
29 | 
30 | BleCompositeHID compositeHID;
31 | GamepadDevice* gamepad;
32 | 
33 | void setup()
34 | {
35 |     Serial.begin(115200);
36 |     Serial.println("Starting BLE work!");
37 | 
38 |     gamepad = new GamepadDevice();
39 |     compositeHID.addDevice(gamepad);
40 | 
41 | 	compositeHID.begin();
42 |     // The default gamepad above enables 16 buttons, all axes, one hat, and no simulation controls or special buttons
43 | }
44 | 
45 | void loop()
46 | {
47 |     if (compositeHID.isConnected())
48 |     {
49 |         Serial.println("Press buttons 5, 16 and start. Move all enabled axes to max. Set DPAD (hat 1) to down right.");
50 |         gamepad->press(BUTTON_5);
51 |         gamepad->press(BUTTON_16);
52 |         gamepad->pressStart();
53 |         gamepad->setAxes(32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767);
54 |         gamepad->setHat1(HAT_DOWN_RIGHT);
55 |         // All axes, sliders, hats etc can also be set independently. See the IndividualAxes.ino example
56 |         delay(500);
57 | 
58 |         Serial.println("Release button 5 and start. Move all axes to min. Set DPAD (hat 1) to centred.");
59 |         gamepad->release(BUTTON_5);
60 |         gamepad->releaseStart();
61 |         gamepad->setHat1(HAT_CENTERED);
62 |         gamepad->setAxes(0, 0, 0, 0, 0, 0, 0, 0);
63 |         delay(500);
64 |     }
65 | }
66 | 


--------------------------------------------------------------------------------
/examples/GamepadExamples/IndividualAxes/IndividualAxes.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * This example turns the ESP32 into a Bluetooth LE gamepad that presses buttons and moves axis
 3 |  *
 4 |  * Possible buttons are:
 5 |  * BUTTON_1 through to BUTTON_128 (Windows gamepad tester only visualises the first 32)
 6 |  ^ Use http://www.planetpointy.co.uk/joystick-test-application/ to visualise all of them
 7 |  * Whenever you adjust the amount of buttons/axes etc, make sure you unpair and repair the BLE device
 8 |  *
 9 |  * Possible DPAD/HAT switch position values are:
10 |  * DPAD_CENTERED, DPAD_UP, DPAD_UP_RIGHT, DPAD_RIGHT, DPAD_DOWN_RIGHT, DPAD_DOWN, DPAD_DOWN_LEFT, DPAD_LEFT, DPAD_UP_LEFT
11 |  *
12 |  * gamepad->setAxes takes the following int16_t parameters for the Left/Right Thumb X/Y, Left/Right Triggers plus slider1 and slider2:
13 |  * (Left Thumb X, Left Thumb Y, Right Thumb X, Right Thumb Y, Left Trigger, Right Trigger, Slider 1, Slider 2);
14 |  *
15 |  * gamepad->setLeftThumb (or setRightThumb) takes 2 int16_t parameters for x and y axes (or z and rZ axes)
16 |  *
17 |  * gamepad->setLeftTrigger (or setRightTrigger) takes 1 int16_t parameter for rX axis (or rY axis)
18 |  *
19 |  * gamepad->setSlider1 (or setSlider2) takes 1 int16_t parameter for slider 1 (or slider 2)
20 |  *
21 |  * gamepad->setHat1 takes a hat position as above (or 0 = centered and 1~8 are the 8 possible directions)
22 |  *
23 |  * setHats, setTriggers and setSliders functions are also available for setting all hats/triggers/sliders at once
24 |  *
25 |  * The example shows that you can set axes/hats independantly, or together.
26 |  *
27 |  * It also shows that you can disable the autoReport feature (enabled by default), and manually call the sendReport() function when wanted
28 |  *
29 |  */
30 | 
31 | #include <Arduino.h>
32 | #include <GamepadDevice.h>
33 | #include <BleCompositeHID.h>
34 | 
35 | BleCompositeHID compositeHID("Controller axis", "lemmingDev", 100);
36 | GamepadDevice* gamepad;
37 | 
38 | void setup()
39 | {
40 |     Serial.begin(115200);
41 |     Serial.println("Starting BLE work!");
42 |     
43 |     GamepadConfiguration bleGamepadConfig;
44 |     bleGamepadConfig.setAutoReport(false); // This is true by default
45 |     bleGamepadConfig.setButtonCount(128);
46 |     bleGamepadConfig.setHatSwitchCount(2);
47 |     
48 |     gamepad = new GamepadDevice(bleGamepadConfig); // Creates a gamepad with 128 buttons, 2 hat switches and x, y, z, rZ, rX, rY and 2 sliders (no simulation controls enabled by default)
49 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
50 | 
51 |     compositeHID.addDevice(gamepad); 
52 | 	compositeHID.begin();
53 | }
54 | 
55 | void loop()
56 | {
57 |     if (compositeHID.isConnected())
58 |     {
59 |         Serial.println("Press buttons 1, 32, 64 and 128. Set hat 1 to down right and hat 2 to up left");
60 | 
61 |         // Press buttons 5, 32, 64 and 128
62 |         gamepad->press(BUTTON_5);
63 |         gamepad->press(BUTTON_32);
64 |         gamepad->press(BUTTON_64);
65 |         gamepad->press(BUTTON_128);
66 | 
67 |         // Move all axes to max.
68 |         gamepad->setLeftThumb(32767, 32767);  // or gamepad->setX(32767); and gamepad->setY(32767);
69 |         gamepad->setRightThumb(32767, 32767); // or gamepad->setZ(32767); and gamepad->setRZ(32767);
70 |         gamepad->setLeftTrigger(32767);       // or gamepad->setRX(32767);
71 |         gamepad->setRightTrigger(32767);      // or gamepad->setRY(32767);
72 |         gamepad->setSlider1(32767);
73 |         gamepad->setSlider2(32767);
74 | 
75 |         // Set hat 1 to down right and hat 2 to up left (hats are otherwise centered by default)
76 |         gamepad->setHat1(DPAD_DOWN_RIGHT); // or gamepad->setHat1(HAT_DOWN_RIGHT);
77 |         gamepad->setHat2(DPAD_UP_LEFT);    // or gamepad->setHat2(HAT_UP_LEFT);
78 |         // Or gamepad->setHats(DPAD_DOWN_RIGHT, DPAD_UP_LEFT);
79 | 
80 |         // Send the gamepad report
81 |         gamepad->sendGamepadReport();
82 |         delay(500);
83 | 
84 |         Serial.println("Release button 5 and 64. Move all axes to min. Set hat 1 and 2 to centred.");
85 |         gamepad->release(BUTTON_5);
86 |         gamepad->release(BUTTON_64);
87 |         gamepad->setAxes(0, 0, 0, 0, 0, 0, 0, 0);
88 |         gamepad->setHats(DPAD_CENTERED, HAT_CENTERED);
89 |         gamepad->sendGamepadReport();
90 |         delay(500);
91 |     }
92 | }
93 | 


--------------------------------------------------------------------------------
/examples/GamepadExamples/Keypad4x4/Keypad4x4.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Shows how to use a 4 x 4 keypad, commonly seen in Arduino starter kits, with the library
 3 |  * https://www.aliexpress.com/item/32879638645.html
 4 |  * It maps the 16 buttons to the first 16 buttons of the controller
 5 |  * Only certain combinations work for multiple presses over 2 buttons
 6 |  */
 7 | 
 8 | #include <Arduino.h>
 9 | #include <Keypad.h>     // https://github.com/Chris--A/Keypad
10 | #include <GamepadDevice.h>
11 | #include <BleCompositeHID.h>
12 | 
13 | // Shows how you can customise the device name, manufacturer name and initial battery level
14 | BleCompositeHID compositeHID("ESP32 Keypad", "lemmingDev", 100);
15 | GamepadDevice* gamepad;
16 | 
17 | #define ROWS 4
18 | #define COLS 4
19 | uint8_t rowPins[ROWS] = {2, 13, 14, 25}; // ESP32 pins used for rows      --> adjust to suit --> Pinout on board: R1, R2, R3, R4
20 | uint8_t colPins[COLS] = {4, 23, 21, 22}; // ESP32 pins used for columns   --> adjust to suit --> Pinout on board: Q1, Q2, Q3, Q4
21 | uint8_t keypadToButtonMap[ROWS][COLS] =
22 |     {
23 |         {1, 2, 3, 4},    // Buttons  1,  2,  3,  4      --> Used for calulating the bitmask for sending to the library
24 |         {5, 6, 7, 8},    // Buttons  5,  6,  7,  8      --> Adjust to suit which buttons you want the library to send
25 |         {9, 10, 11, 12}, // Buttons  9, 10, 11, 12      -->
26 |         {13, 14, 15, 16} // Buttons 13, 14, 15, 16      --> Eg. The value 12 in the array refers to button 12
27 | };
28 | 
29 | Keypad customKeypad = Keypad(makeKeymap(keypadToButtonMap), rowPins, colPins, ROWS, COLS);
30 | 
31 | void setup()
32 | {
33 |     GamepadConfiguration bleGamepadConfig;
34 |     bleGamepadConfig.setAutoReport(false); // Disable auto reports --> You then need to force HID updates with bleGamepad.sendReport()
35 |     
36 |     gamepad = new GamepadDevice(bleGamepadConfig); // Creates a gamepad with 128 buttons, 2 hat switches and x, y, z, rZ, rX, rY and 2 sliders (no simulation controls enabled by default)
37 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
38 | 
39 |     compositeHID.addDevice(gamepad); 
40 | 	compositeHID.begin();
41 | }
42 | 
43 | void loop()
44 | {
45 |     KeypadUpdate();
46 |     delay(10);
47 | }
48 | 
49 | void KeypadUpdate()
50 | {
51 |     customKeypad.getKeys();
52 | 
53 |     for (int i = 0; i < LIST_MAX; i++) // Scan the whole key list.      //LIST_MAX is provided by the Keypad library and gives the number of buttons of the Keypad instance
54 |     {
55 |         if (customKeypad.key[i].stateChanged) // Only find keys that have changed state.
56 |         {
57 |             uint8_t keystate = customKeypad.key[i].kstate;
58 | 
59 |             if (compositeHID.isConnected())
60 |             {
61 |                 if (keystate == PRESSED)
62 |                 {
63 |                     gamepad->press(customKeypad.key[i].kchar);
64 |                 } // Press or release button based on the current state
65 |                 if (keystate == RELEASED)
66 |                 {
67 |                     gamepad->release(customKeypad.key[i].kchar);
68 |                 }
69 | 
70 |                 gamepad->sendGamepadReport(); // Send the HID report after values for all button states are updated, and at least one button state had changed
71 |             }
72 |         }
73 |     }
74 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/MultipleButtons/MultipleButtons.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * This code programs a number of pins on an ESP32 as buttons on a BLE gamepad
 3 |  *
 4 |  * It uses arrays to cut down on code
 5 |  *
 6 |  * Before using, adjust the numOfButtons, buttonPins and physicalButtons to suit your senario
 7 |  *
 8 |  */
 9 | 
10 | #include <Arduino.h>
11 | #include <GamepadDevice.h>
12 | #include <BleCompositeHID.h>
13 | 
14 | BleCompositeHID compositeHID;
15 | GamepadDevice* gamepad;
16 | 
17 | #define numOfButtons 10
18 | 
19 | byte previousButtonStates[numOfButtons];
20 | byte currentButtonStates[numOfButtons];
21 | byte buttonPins[numOfButtons] = {0, 35, 17, 18, 19, 23, 25, 26, 27, 32};
22 | byte physicalButtons[numOfButtons] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
23 | 
24 | void setup()
25 | {
26 |     for (byte currentPinIndex = 0; currentPinIndex < numOfButtons; currentPinIndex++)
27 |     {
28 |         pinMode(buttonPins[currentPinIndex], INPUT_PULLUP);
29 |         previousButtonStates[currentPinIndex] = HIGH;
30 |         currentButtonStates[currentPinIndex] = HIGH;
31 |     }
32 | 
33 |     GamepadConfiguration bleGamepadConfig;
34 |     bleGamepadConfig.setAutoReport(false);
35 |     bleGamepadConfig.setButtonCount(numOfButtons);
36 |     gamepad = new GamepadDevice(bleGamepadConfig);
37 | 
38 |     compositeHID.addDevice(gamepad);
39 |     compositeHID.begin();
40 | 
41 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
42 | }
43 | 
44 | void loop()
45 | {
46 |     if (compositeHID.isConnected())
47 |     {
48 |         for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
49 |         {
50 |             currentButtonStates[currentIndex] = digitalRead(buttonPins[currentIndex]);
51 | 
52 |             if (currentButtonStates[currentIndex] != previousButtonStates[currentIndex])
53 |             {
54 |                 if (currentButtonStates[currentIndex] == LOW)
55 |                 {
56 |                     gamepad->press(physicalButtons[currentIndex]);
57 |                 }
58 |                 else
59 |                 {
60 |                     gamepad->release(physicalButtons[currentIndex]);
61 |                 }
62 |             }
63 |         }
64 | 
65 |         if (currentButtonStates != previousButtonStates)
66 |         {
67 |             for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
68 |             {
69 |                 previousButtonStates[currentIndex] = currentButtonStates[currentIndex];
70 |             }
71 | 
72 |             gamepad->sendGamepadReport();
73 |         }
74 | 
75 |         delay(20);
76 |     }
77 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/MultipleButtonsAndHats/MultipleButtonsAndHats.ino:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * This code programs a number of pins on an ESP32 as buttons and hats on a BLE gamepad
  3 |  *
  4 |  * It uses arrays to cut down on code
  5 |  *
  6 |  * Before using, adjust the numOfButtons, buttonPins, physicalButtons, numOfHats and hatPins to suit your scenario
  7 |  *
  8 |  */
  9 | 
 10 | #include <Arduino.h>
 11 | #include <GamepadDevice.h>
 12 | #include <BleCompositeHID.h>
 13 | 
 14 | BleCompositeHID compositeHID;
 15 | GamepadDevice* gamepad;
 16 | 
 17 | #define numOfButtons 4
 18 | #define numOfHats 1 // Maximum 4 hat switches supported
 19 | 
 20 | byte previousButtonStates[numOfButtons];
 21 | byte currentButtonStates[numOfButtons];
 22 | byte buttonPins[numOfButtons] = {18, 19, 25, 26};
 23 | byte physicalButtons[numOfButtons] = {1, 2, 3, 4};
 24 | 
 25 | byte previousHatStates[numOfHats * 4];
 26 | byte currentHatStates[numOfHats * 4];
 27 | byte hatPins[numOfHats * 4] = {0, 23, 18, 35}; // in order UP, LEFT, DOWN, RIGHT. 4 pins per hat switch (Eg. List 12 pins if there are 3 hat switches)
 28 | 
 29 | void setup()
 30 | {
 31 |     // Setup Buttons
 32 |     for (byte currentPinIndex = 0; currentPinIndex < numOfButtons; currentPinIndex++)
 33 |     {
 34 |         pinMode(buttonPins[currentPinIndex], INPUT_PULLUP);
 35 |         previousButtonStates[currentPinIndex] = HIGH;
 36 |         currentButtonStates[currentPinIndex] = HIGH;
 37 |     }
 38 | 
 39 |     // Setup Hat Switches
 40 |     for (byte currentPinIndex = 0; currentPinIndex < numOfHats * 4; currentPinIndex++)
 41 |     {
 42 |         pinMode(hatPins[currentPinIndex], INPUT_PULLUP);
 43 |         previousHatStates[currentPinIndex] = HIGH;
 44 |         currentHatStates[currentPinIndex] = HIGH;
 45 |     }
 46 | 
 47 |     GamepadConfiguration bleGamepadConfig;
 48 |     bleGamepadConfig.setAutoReport(false);
 49 |     bleGamepadConfig.setButtonCount(numOfButtons);
 50 |     bleGamepadConfig.setHatSwitchCount(numOfHats);
 51 |     gamepad = new GamepadDevice(bleGamepadConfig);
 52 | 
 53 |     compositeHID.addDevice(gamepad);
 54 |     compositeHID.begin();
 55 | 
 56 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
 57 | }
 58 | 
 59 | void loop()
 60 | {
 61 |     if (compositeHID.isConnected())
 62 |     {
 63 |         // Deal with buttons
 64 |         for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
 65 |         {
 66 |             currentButtonStates[currentIndex] = digitalRead(buttonPins[currentIndex]);
 67 | 
 68 |             if (currentButtonStates[currentIndex] != previousButtonStates[currentIndex])
 69 |             {
 70 |                 if (currentButtonStates[currentIndex] == LOW)
 71 |                 {
 72 |                     gamepad->press(physicalButtons[currentIndex]);
 73 |                 }
 74 |                 else
 75 |                 {
 76 |                     gamepad->release(physicalButtons[currentIndex]);
 77 |                 }
 78 |             }
 79 |         }
 80 | 
 81 |         // Update hat switch pin states
 82 |         for (byte currentHatPinsIndex = 0; currentHatPinsIndex < numOfHats * 4; currentHatPinsIndex++)
 83 |         {
 84 |             currentHatStates[currentHatPinsIndex] = digitalRead(hatPins[currentHatPinsIndex]);
 85 |         }
 86 | 
 87 |         // Update hats
 88 |         signed char hatValues[4] = {0, 0, 0, 0};
 89 | 
 90 |         for (byte currentHatIndex = 0; currentHatIndex < numOfHats; currentHatIndex++)
 91 |         {
 92 |             signed char hatValueToSend = 0;
 93 | 
 94 |             for (byte currentHatPin = 0; currentHatPin < 4; currentHatPin++)
 95 |             {
 96 |                 // Check for direction
 97 |                 if (currentHatStates[currentHatPin + currentHatIndex * 4] == LOW)
 98 |                 {
 99 |                     hatValueToSend = currentHatPin * 2 + 1;
100 | 
101 |                     // Account for last diagonal
102 |                     if (currentHatPin == 0)
103 |                     {
104 |                         if (currentHatStates[currentHatIndex * 4 + 3] == LOW)
105 |                         {
106 |                             hatValueToSend = 8;
107 |                             break;
108 |                         }
109 |                     }
110 | 
111 |                     // Account for first 3 diagonals
112 |                     if (currentHatPin < 3)
113 |                     {
114 |                         if (currentHatStates[currentHatPin + currentHatIndex * 4 + 1] == LOW)
115 |                         {
116 |                             hatValueToSend += 1;
117 |                             break;
118 |                         }
119 |                     }
120 |                 }
121 |             }
122 | 
123 |             hatValues[currentHatIndex] = hatValueToSend;
124 |         }
125 | 
126 |         // Set hat values
127 |         gamepad->setHats(hatValues[0], hatValues[1], hatValues[2], hatValues[3]);
128 | 
129 |         // Update previous states to current states and send report
130 |         if (currentButtonStates != previousButtonStates || currentHatStates != previousHatStates)
131 |         {
132 |             for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
133 |             {
134 |                 previousButtonStates[currentIndex] = currentButtonStates[currentIndex];
135 |             }
136 | 
137 |             for (byte currentIndex = 0; currentIndex < numOfHats * 4; currentIndex++)
138 |             {
139 |                 previousHatStates[currentIndex] = currentHatStates[currentIndex];
140 |             }
141 | 
142 |             gamepad->sendGamepadReport();
143 |         }
144 | 
145 |         delay(20);
146 |     }
147 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/MultipleButtonsDebounce/MultipleButtonsDebounce.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * This code programs a number of pins on an ESP32 as buttons on a BLE gamepad
 3 |  *
 4 |  * It uses arrays to cut down on code
 5 |  *
 6 |  * Uses the Bounce2 library to debounce all buttons
 7 |  *
 8 |  * Uses the rose/fell states of the Bounce instance to track button states
 9 |  *
10 |  * Before using, adjust the numOfButtons, buttonPins and physicalButtons to suit your senario
11 |  *
12 |  */
13 | 
14 | #define BOUNCE_WITH_PROMPT_DETECTION // Make button state changes available immediately
15 | 
16 | #include <Arduino.h>
17 | #include <Bounce2.h>    // https://github.com/thomasfredericks/Bounce2
18 | #include <GamepadDevice.h>
19 | #include <BleCompositeHID.h>
20 | 
21 | BleCompositeHID compositeHID;
22 | GamepadDevice* gamepad;
23 | 
24 | #define numOfButtons 10
25 | 
26 | Bounce debouncers[numOfButtons];
27 | 
28 | byte buttonPins[numOfButtons] = {0, 35, 17, 18, 19, 23, 25, 26, 27, 32};
29 | byte physicalButtons[numOfButtons] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
30 | 
31 | void setup()
32 | {
33 |     Serial.begin(115200);
34 | 
35 |     for (byte currentPinIndex = 0; currentPinIndex < numOfButtons; currentPinIndex++)
36 |     {
37 |         pinMode(buttonPins[currentPinIndex], INPUT_PULLUP);
38 | 
39 |         debouncers[currentPinIndex] = Bounce();
40 |         debouncers[currentPinIndex].attach(buttonPins[currentPinIndex]); // After setting up the button, setup the Bounce instance :
41 |         debouncers[currentPinIndex].interval(5);
42 |     }
43 | 
44 |     GamepadConfiguration bleGamepadConfig;
45 |     bleGamepadConfig.setButtonCount(numOfButtons);
46 |     bleGamepadConfig.setAutoReport(false);
47 | 
48 |     gamepad = new GamepadDevice(bleGamepadConfig);
49 | 
50 |     compositeHID.addDevice(gamepad);
51 |     compositeHID.begin();
52 | 
53 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
54 | }
55 | 
56 | void loop()
57 | {
58 |     if (compositeHID.isConnected())
59 |     {
60 |         bool sendReport = false;
61 | 
62 |         for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
63 |         {
64 |             debouncers[currentIndex].update();
65 | 
66 |             if (debouncers[currentIndex].fell())
67 |             {
68 |                 gamepad->press(physicalButtons[currentIndex]);
69 |                 sendReport = true;
70 |                 Serial.print("Button ");
71 |                 Serial.print(physicalButtons[currentIndex]);
72 |                 Serial.println(" pushed.");
73 |             }
74 |             else if (debouncers[currentIndex].rose())
75 |             {
76 |                 gamepad->release(physicalButtons[currentIndex]);
77 |                 sendReport = true;
78 |                 Serial.print("Button ");
79 |                 Serial.print(physicalButtons[currentIndex]);
80 |                 Serial.println(" released.");
81 |             }
82 |         }
83 | 
84 |         if (sendReport)
85 |         {
86 |             gamepad->sendGamepadReport();
87 |         }
88 | 
89 |         // delay(20);	// (Un)comment to remove/add delay between loops
90 |     }
91 | }
92 | 


--------------------------------------------------------------------------------
/examples/GamepadExamples/PlayerIndicators/PlayerIndicators.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * A simple sketch that maps a single pin on the ESP32 to a single button on the controller
 3 |  */
 4 | 
 5 | #include <Arduino.h>
 6 | #include <GamepadDevice.h>
 7 | #include <BleCompositeHID.h>
 8 | 
 9 | BleCompositeHID compositeHID("Player indicator gamepad", "Mystfit", 100);
10 | GamepadDevice* gamepad;
11 | 
12 | void OnPlayerIndicatorChanged(uint8_t playerIndicator)
13 | {
14 |     Serial.println("Player indicator changed to " + String(playerIndicator));
15 | }
16 | 
17 | void setup()
18 | {
19 |     GamepadConfiguration gamepadConfig;
20 |     gamepadConfig.setIncludePlayerIndicators(true);
21 |     gamepad = new GamepadDevice(gamepadConfig);
22 | 
23 |     FunctionSlot<uint8_t> playerIndicatorChangedSlot(OnPlayerIndicatorChanged);
24 |     gamepad->onPlayerIndicatorChanged.attach(playerIndicatorChangedSlot);
25 | 
26 |     compositeHID.addDevice(gamepad);
27 |     compositeHID.begin();
28 | }
29 | 
30 | void loop()
31 | {
32 |     if (compositeHID.isConnected())
33 |     {
34 |         delay(1000);
35 |     }
36 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/PotAsAxis/PotAsAxis.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Reads a potentiometer on pin 34 and maps the reading to the X axis
 3 |  *
 4 |  * Potentiometers can be noisy, so the sketch can take multiple samples to average out the readings
 5 |  */
 6 | 
 7 | #include <Arduino.h>
 8 | #include <GamepadDevice.h>
 9 | #include <BleCompositeHID.h>
10 | 
11 | BleCompositeHID compositeHID;
12 | GamepadDevice* gamepad;
13 | 
14 | const int potPin = 34;                // Potentiometer is connected to GPIO 34 (Analog ADC1_CH6)
15 | const int numberOfPotSamples = 5;     // Number of pot samples to take (to smooth the values)
16 | const int delayBetweenSamples = 4;    // Delay in milliseconds between pot samples
17 | const int delayBetweenHIDReports = 5; // Additional delay in milliseconds between HID reports
18 | 
19 | void setup()
20 | {
21 |     Serial.begin(115200);
22 |     Serial.println("Starting BLE work!");
23 |     
24 |     gamepad = new GamepadDevice();
25 |     compositeHID.addDevice(gamepad);
26 | 
27 |     compositeHID.addDevice(gamepad);
28 |     compositeHID.begin();
29 | }
30 | 
31 | void loop()
32 | {
33 |     if (compositeHID.isConnected())
34 |     {
35 |         int potValues[numberOfPotSamples]; // Array to store pot readings
36 |         int potValue = 0;                  // Variable to store calculated pot reading average
37 | 
38 |         // Populate readings
39 |         for (int i = 0; i < numberOfPotSamples; i++)
40 |         {
41 |             potValues[i] = analogRead(potPin);
42 |             potValue += potValues[i];
43 |             delay(delayBetweenSamples);
44 |         }
45 | 
46 |         // Calculate the average
47 |         potValue = potValue / numberOfPotSamples;
48 | 
49 |         // Map analog reading from 0 ~ 4095 to 32737 ~ 0 for use as an axis reading
50 |         int adjustedValue = map(potValue, 0, 4095, 32737, 0);
51 | 
52 |         // Update X axis and auto-send report
53 |         gamepad->setX(adjustedValue);
54 |         delay(delayBetweenHIDReports);
55 | 
56 |         // The code below (apart from the 2 closing braces) is for pot value degugging, and can be removed
57 |         // Print readings to serial port
58 |         Serial.print("Sent: ");
59 |         Serial.print(adjustedValue);
60 |         Serial.print("\tRaw Avg: ");
61 |         Serial.print(potValue);
62 |         Serial.print("\tRaw: {");
63 | 
64 |         // Iterate through raw pot values, printing them to the serial port
65 |         for (int i = 0; i < numberOfPotSamples; i++)
66 |         {
67 |             Serial.print(potValues[i]);
68 | 
69 |             // Format the values into a comma seperated list
70 |             if (i == numberOfPotSamples - 1)
71 |             {
72 |                 Serial.println("}");
73 |             }
74 |             else
75 |             {
76 |                 Serial.print(", ");
77 |             }
78 |         }
79 |     }
80 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/SingleButton/SingleButton.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * A simple sketch that maps a single pin on the ESP32 to a single button on the controller
 3 |  */
 4 | 
 5 | #include <Arduino.h>
 6 | #include <GamepadDevice.h>
 7 | #include <BleCompositeHID.h>
 8 | 
 9 | #define BUTTONPIN 35 // Pin button is attached to
10 | 
11 | BleCompositeHID compositeHID;
12 | GamepadDevice* gamepad;
13 | 
14 | int previousButton1State = HIGH;
15 | 
16 | void setup()
17 | {
18 |     pinMode(BUTTONPIN, INPUT_PULLUP);
19 |     gamepad = new GamepadDevice();
20 |     compositeHID.addDevice(gamepad);
21 |     compositeHID.begin();
22 | }
23 | 
24 | void loop()
25 | {
26 |     if (compositeHID.isConnected())
27 |     {
28 | 
29 |         int currentButton1State = digitalRead(BUTTONPIN);
30 | 
31 |         if (currentButton1State != previousButton1State)
32 |         {
33 |             if (currentButton1State == LOW)
34 |             {
35 |                 gamepad->press(BUTTON_1);
36 |             }
37 |             else
38 |             {
39 |                 gamepad->release(BUTTON_1);
40 |             }
41 |         }
42 |         previousButton1State = currentButton1State;
43 |     }
44 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/SingleButtonDebounce/SingleButtonDebounce.ino:
--------------------------------------------------------------------------------
 1 | #include <Arduino.h>
 2 | #include <Bounce2.h>    // https://github.com/thomasfredericks/Bounce2
 3 | #include <GamepadDevice.h>
 4 | #include <BleCompositeHID.h>
 5 | 
 6 | #define BOUNCE_WITH_PROMPT_DETECTION // Make button state changes available immediately
 7 | #define BUTTON_PIN 35
 8 | #define LED_PIN 13
 9 | 
10 | Bounce debouncer = Bounce(); // Instantiate a Bounce object
11 | 
12 | BleCompositeHID compositeHID;
13 | GamepadDevice* gamepad;
14 | 
15 | void setup()
16 | {
17 |     pinMode(BUTTON_PIN, INPUT_PULLUP); // Setup the button with an internal pull-up
18 | 
19 |     debouncer.attach(BUTTON_PIN); // After setting up the button, setup the Bounce instance :
20 |     debouncer.interval(5);        // interval in ms
21 | 
22 |     pinMode(LED_PIN, OUTPUT); // Setup the LED :
23 | 
24 |     gamepad = new GamepadDevice();
25 |     compositeHID.addDevice(gamepad);
26 |     compositeHID.begin();
27 | }
28 | 
29 | void loop()
30 | {
31 |     if (compositeHID.isConnected())
32 |     {
33 |         debouncer.update(); // Update the Bounce instance
34 | 
35 |         int value = debouncer.read(); // Get the updated value
36 | 
37 |         // Press/release gamepad button and turn on or off the LED as determined by the state
38 |         if (value == LOW)
39 |         {
40 |             digitalWrite(LED_PIN, HIGH);
41 |             gamepad->press(BUTTON_1);
42 |         }
43 |         else
44 |         {
45 |             digitalWrite(LED_PIN, LOW);
46 |             gamepad->release(BUTTON_1);
47 |         }
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/examples/GamepadExamples/SpecialButtons/SpecialButtons.ino:
--------------------------------------------------------------------------------
 1 | #include <Arduino.h>
 2 | #include <GamepadDevice.h>
 3 | #include <BleCompositeHID.h>
 4 | 
 5 | BleCompositeHID compositeHID;
 6 | GamepadDevice* gamepad;
 7 | 
 8 | void setup()
 9 | {
10 |     Serial.begin(115200);
11 | 
12 |     GamepadConfiguration bleGamepadConfig;
13 |     bleGamepadConfig.setWhichSpecialButtons(true, true, true, true, true, true, true, true);
14 |     // Can also enable special buttons individually with the following <-- They are all disabled by default
15 |     // bleGamepadConfig.setIncludeStart(true);
16 |     // bleGamepadConfig.setIncludeSelect(true);
17 |     // bleGamepadConfig.setIncludeMenu(true);
18 |     // bleGamepadConfig.setIncludeHome(true);
19 |     // bleGamepadConfig.setIncludeBack(true);
20 |     // bleGamepadConfig.setIncludeVolumeInc(true);
21 |     // bleGamepadConfig.setIncludeVolumeDec(true);
22 |     // bleGamepadConfig.setIncludeVolumeMute(true);
23 | 
24 |     gamepad = new GamepadDevice(bleGamepadConfig);
25 |     compositeHID.addDevice(gamepad);
26 |     compositeHID.begin();
27 |     // Changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
28 | }
29 | 
30 | void loop()
31 | {
32 |     if (compositeHID.isConnected())
33 |     {
34 |         Serial.println("Pressing start and select");
35 |         gamepad->pressStart();
36 |         delay(100);
37 |         gamepad->releaseStart();
38 |         delay(100);
39 |         gamepad->pressSelect();
40 |         delay(100);
41 |         gamepad->releaseSelect();
42 |         delay(100);
43 | 
44 |         Serial.println("Increasing volume");
45 |         gamepad->pressVolumeInc();
46 |         delay(100);
47 |         gamepad->releaseVolumeInc();
48 |         delay(100);
49 |         gamepad->pressVolumeInc();
50 |         delay(100);
51 |         gamepad->releaseVolumeInc();
52 |         delay(100);
53 |         
54 |         Serial.println("Muting volume");
55 |         gamepad->pressVolumeMute();
56 |         delay(100);
57 |         gamepad->releaseVolumeMute();
58 |         delay(1000);
59 |         gamepad->pressVolumeMute();
60 |         delay(100);
61 |         gamepad->releaseVolumeMute();
62 | 
63 | 
64 |         Serial.println("Pressing menu and back");
65 |         gamepad->pressMenu();
66 |         delay(100);
67 |         gamepad->releaseMenu();
68 |         delay(100);
69 |         gamepad->pressBack();
70 |         delay(100);
71 |         gamepad->releaseBack();
72 |         delay(100);
73 | 
74 |         Serial.println("Pressing home");
75 |         gamepad->pressHome();
76 |         delay(100);
77 |         gamepad->releaseHome();
78 |         delay(2000);
79 |     }
80 | }
81 | 


--------------------------------------------------------------------------------
/examples/GamepadExamples/TestAll/TestAll.ino:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Test all gamepad buttons, axes and dpad
  3 |  */
  4 | 
  5 | #include <Arduino.h>
  6 | #include <GamepadDevice.h>
  7 | #include <BleCompositeHID.h>
  8 | 
  9 | #define numOfButtons 64
 10 | #define numOfHatSwitches 4
 11 | 
 12 | BleCompositeHID compositeHID;
 13 | GamepadDevice* gamepad;
 14 | GamepadConfiguration bleGamepadConfig;
 15 | 
 16 | void setup()
 17 | {
 18 |     Serial.begin(115200);
 19 |     Serial.println("Starting BLE work!");
 20 |     
 21 |     BLEHostConfiguration hostConfig;
 22 |     hostConfig.setVid(0xe502);
 23 |     hostConfig.setPid(0xabcd);
 24 | 
 25 |     bleGamepadConfig.setAutoReport(false);
 26 |     bleGamepadConfig.setControllerType(CONTROLLER_TYPE_GAMEPAD); // CONTROLLER_TYPE_JOYSTICK, CONTROLLER_TYPE_GAMEPAD (DEFAULT), CONTROLLER_TYPE_MULTI_AXIS
 27 |     bleGamepadConfig.setButtonCount(numOfButtons);
 28 |     bleGamepadConfig.setHatSwitchCount(numOfHatSwitches);
 29 |     // Some non-Windows operating systems and web based gamepad testers don't like min axis set below 0, so 0 is set by default
 30 |     //bleGamepadConfig.setAxesMin(0x8001); // -32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
 31 | 	bleGamepadConfig.setAxesMin(0x0000); // 0 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
 32 |     bleGamepadConfig.setAxesMax(0x7FFF); // 32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal 
 33 |     
 34 |     gamepad = new GamepadDevice(bleGamepadConfig); // Simulation controls, special buttons and hats 2/3/4 are disabled by default
 35 |     compositeHID.addDevice(gamepad);
 36 |     compositeHID.begin();
 37 |     // changing bleGamepadConfig after the begin function has no effect, unless you call the begin function again
 38 | }
 39 | 
 40 | void loop()
 41 | {
 42 |     if (compositeHID.isConnected())
 43 |     {
 44 |         Serial.println("\nn--- Axes Decimal ---");
 45 |         Serial.print("Axes Min: ");
 46 |         Serial.println(bleGamepadConfig.getAxesMin());
 47 |         Serial.print("Axes Max: ");
 48 |         Serial.println(bleGamepadConfig.getAxesMax());
 49 |         Serial.println("\nn--- Axes Hex ---");
 50 |         Serial.print("Axes Min: ");
 51 |         Serial.println(bleGamepadConfig.getAxesMin(), HEX);
 52 |         Serial.print("Axes Max: ");
 53 |         Serial.println(bleGamepadConfig.getAxesMax(), HEX);        
 54 |         
 55 |         Serial.println("\n\nPress all buttons one by one");
 56 |         for (int i = 1; i <= numOfButtons; i += 1)
 57 |         {
 58 |             gamepad->press(i);
 59 |             gamepad->sendGamepadReport();
 60 |             delay(100);
 61 |             gamepad->release(i);
 62 |             gamepad->sendGamepadReport();
 63 |             delay(25);
 64 |         }
 65 | 
 66 |         Serial.println("Press start and select");
 67 |         gamepad->pressStart();
 68 |         gamepad->sendGamepadReport();
 69 |         delay(100);
 70 |         gamepad->pressSelect();
 71 |         gamepad->sendGamepadReport();
 72 |         delay(100);
 73 |         gamepad->releaseStart();
 74 |         gamepad->sendGamepadReport();
 75 |         delay(100);
 76 |         gamepad->releaseSelect();
 77 |         gamepad->sendGamepadReport();
 78 | 
 79 |         Serial.println("Move all axis simultaneously from min to max");
 80 |         for (int i = bleGamepadConfig.getAxesMin(); i < bleGamepadConfig.getAxesMax(); i += (bleGamepadConfig.getAxesMax() / 256) + 1)
 81 |         {
 82 |             gamepad->setAxes(i, i, i, i, i, i);
 83 |             gamepad->sendGamepadReport();
 84 |             delay(10);
 85 |         }
 86 |         gamepad->setAxes(); // Reset all axes to zero
 87 |         gamepad->sendGamepadReport();
 88 | 
 89 |         Serial.println("Move all sliders simultaneously from min to max");
 90 |        for (int i = bleGamepadConfig.getAxesMin(); i < bleGamepadConfig.getAxesMax(); i += (bleGamepadConfig.getAxesMax() / 256) + 1)
 91 |         {
 92 |             gamepad->setSliders(i, i);
 93 |             gamepad->sendGamepadReport();
 94 |             delay(10);
 95 |         }
 96 |         gamepad->setSliders(); // Reset all sliders to zero
 97 |         gamepad->sendGamepadReport();
 98 | 
 99 |         Serial.println("Send hat switch 1 one by one in an anticlockwise rotation");
100 |         for (int i = 8; i >= 0; i--)
101 |         {
102 |             gamepad->setHat1(i);
103 |             gamepad->sendGamepadReport();
104 |             delay(200);
105 |         }
106 | 
107 |         Serial.println("Send hat switch 2 one by one in an anticlockwise rotation");
108 |         for (int i = 8; i >= 0; i--)
109 |         {
110 |             gamepad->setHat2(i);
111 |             gamepad->sendGamepadReport();
112 |             delay(200);
113 |         }
114 | 
115 |         Serial.println("Send hat switch 3 one by one in an anticlockwise rotation");
116 |         for (int i = 8; i >= 0; i--)
117 |         {
118 |             gamepad->setHat3(i);
119 |             gamepad->sendGamepadReport();
120 |             delay(200);
121 |         }
122 | 
123 |         Serial.println("Send hat switch 4 one by one in an anticlockwise rotation");
124 |         for (int i = 8; i >= 0; i--)
125 |         {
126 |             gamepad->setHat4(i);
127 |             gamepad->sendGamepadReport();
128 |             delay(200);
129 |         }
130 | 
131 |         //    Simulation controls are disabled by default
132 |         //    Serial.println("Move all simulation controls simultaneously from min to max");
133 |         //    for (int i = bleGamepadConfig.getSimulationMin(); i < bleGamepadConfig.getSimulationMax(); i += (bleGamepadConfig.getAxesMax() / 256) + 1)
134 |         //    {
135 |         //      gamepad->setRudder(i);
136 |         //      gamepad->setThrottle(i);
137 |         //      gamepad->setAccelerator(i);
138 |         //      gamepad->setBrake(i);
139 |         //      gamepad->setSteering(i);
140 |         //      gamepad->sendGamepadReport();
141 |         //      delay(10);
142 |         //    }
143 |         //    gamepad->setSimulationControls(); //Reset all simulation controls to zero
144 |         gamepad->sendGamepadReport();
145 |     }
146 | }


--------------------------------------------------------------------------------
/examples/GamepadExamples/XboxXInputController/XboxXInputController.ino:
--------------------------------------------------------------------------------
  1 | #include <BleConnectionStatus.h>
  2 | 
  3 | #include <BleCompositeHID.h>
  4 | #include <XboxGamepadDevice.h>
  5 | 
  6 | int ledPin = 5; // LED connected to digital pin 13
  7 | 
  8 | XboxGamepadDevice *gamepad;
  9 | BleCompositeHID compositeHID("ESP32 SeriesX Controller", "Mystfit", 100);
 10 | 
 11 | void OnVibrateEvent(XboxGamepadOutputReportData data)
 12 | {
 13 |     if(data.weakMotorMagnitude > 0 || data.strongMotorMagnitude > 0){
 14 |         digitalWrite(ledPin, LOW);
 15 |     } else {
 16 |         digitalWrite(ledPin, HIGH);
 17 |     }
 18 |     Serial.println("Vibration event. Weak motor: " + String(data.weakMotorMagnitude) + " Strong motor: " + String(data.strongMotorMagnitude));
 19 | }
 20 | 
 21 | void setup()
 22 | {
 23 |     Serial.begin(115200);
 24 |     pinMode(ledPin, OUTPUT); // sets the digital pin as output
 25 | 
 26 |     // Uncomment one of the following two config types depending on which controller version you want to use
 27 |     // The XBox series X controller only works on linux kernels >= 6.5
 28 |     
 29 |     //XboxOneSControllerDeviceConfiguration* config = new XboxOneSControllerDeviceConfiguration();
 30 |     XboxSeriesXControllerDeviceConfiguration* config = new XboxSeriesXControllerDeviceConfiguration();
 31 | 
 32 |     // The composite HID device pretends to be a valid Xbox controller via vendor and product IDs (VID/PID).
 33 |     // Platforms like windows/linux need this in order to pick an XInput driver over the generic BLE GATT HID driver. 
 34 |     BLEHostConfiguration hostConfig = config->getIdealHostConfiguration();
 35 |     Serial.println("Using VID source: " + String(hostConfig.getVidSource(), HEX));
 36 |     Serial.println("Using VID: " + String(hostConfig.getVid(), HEX));
 37 |     Serial.println("Using PID: " + String(hostConfig.getPid(), HEX));
 38 |     Serial.println("Using GUID version: " + String(hostConfig.getGuidVersion(), HEX));
 39 |     Serial.println("Using serial number: " + String(hostConfig.getSerialNumber()));
 40 |     
 41 |     // Set up gamepad
 42 |     gamepad = new XboxGamepadDevice(config);
 43 | 
 44 |     // Set up vibration event handler
 45 |     FunctionSlot<XboxGamepadOutputReportData> vibrationSlot(OnVibrateEvent);
 46 |     gamepad->onVibrate.attach(vibrationSlot);
 47 | 
 48 |     // Add all child devices to the top-level composite HID device to manage them
 49 |     compositeHID.addDevice(gamepad);
 50 | 
 51 |     // Start the composite HID device to broadcast HID reports
 52 |     Serial.println("Starting composite HID device...");
 53 |     compositeHID.begin(hostConfig);
 54 | }
 55 | 
 56 | void loop()
 57 | {
 58 |     if(compositeHID.isConnected()){
 59 |         testButtons();
 60 |         testPads();
 61 |         testTriggers();
 62 |         testThumbsticks();
 63 |     }
 64 | }
 65 | 
 66 | void testButtons(){
 67 |     // Test each button
 68 |     uint16_t buttons[] = {
 69 |         XBOX_BUTTON_A, 
 70 |         XBOX_BUTTON_B, 
 71 |         XBOX_BUTTON_X, 
 72 |         XBOX_BUTTON_Y, 
 73 |         XBOX_BUTTON_LB, 
 74 |         XBOX_BUTTON_RB, 
 75 |         XBOX_BUTTON_START,
 76 |         XBOX_BUTTON_SELECT,
 77 |         //XBOX_BUTTON_HOME,   // Uncomment this to test the hom/guide button. Steam will flip out and enter big picture mode when running this sketch though so be warned!
 78 |         XBOX_BUTTON_LS, 
 79 |         XBOX_BUTTON_RS
 80 |     };
 81 |     for (uint16_t button : buttons)
 82 |     {
 83 |         Serial.println("Pressing button " + String(button));
 84 |         gamepad->press(button);
 85 |         gamepad->sendGamepadReport();
 86 |         delay(500);
 87 |         gamepad->release(button);
 88 |         gamepad->sendGamepadReport();
 89 |         delay(100);
 90 |     }
 91 | 
 92 |     // The share button is a seperate call since it doesn't live in the same 
 93 |     // bitflag as the rest of the buttons
 94 |     gamepad->pressShare();
 95 |     gamepad->sendGamepadReport();
 96 |     delay(500);
 97 |     gamepad->releaseShare();
 98 |     gamepad->sendGamepadReport();
 99 |     delay(100);
100 | }
101 | 
102 | void testPads(){
103 |     XboxDpadFlags directions[] = {
104 |         XboxDpadFlags::NORTH,
105 |         XboxDpadFlags((uint8_t)XboxDpadFlags::NORTH | (uint8_t)XboxDpadFlags::EAST),
106 |         XboxDpadFlags::EAST,
107 |         XboxDpadFlags((uint8_t)XboxDpadFlags::EAST | (uint8_t)XboxDpadFlags::SOUTH),
108 |         XboxDpadFlags::SOUTH,
109 |         XboxDpadFlags((uint8_t)XboxDpadFlags::SOUTH | (uint8_t)XboxDpadFlags::WEST),
110 |         XboxDpadFlags::WEST,
111 |         XboxDpadFlags((uint8_t)XboxDpadFlags::WEST | (uint8_t)XboxDpadFlags::NORTH)
112 |     };
113 | 
114 |     for (XboxDpadFlags direction : directions)
115 |     {
116 |         Serial.println("Pressing DPad: " + String(direction));
117 |         gamepad->pressDPadDirectionFlag(direction);
118 |         gamepad->sendGamepadReport();
119 |         delay(500);
120 |         gamepad->releaseDPad();
121 |         gamepad->sendGamepadReport();
122 |         delay(100);
123 |     }
124 | }
125 | 
126 | void testTriggers(){
127 |     for(int16_t val = XBOX_TRIGGER_MIN; val <= XBOX_TRIGGER_MAX; val++){
128 |         if(val % 8 == 0)
129 |             Serial.println("Setting trigger value to " + String(val));
130 |         gamepad->setLeftTrigger(val);
131 |         gamepad->setRightTrigger(val);
132 |         gamepad->sendGamepadReport();
133 |         delay(8);
134 |     }
135 | }
136 | 
137 | void testThumbsticks(){
138 |     int startTime = millis();
139 |     int reportCount = 0;
140 |     while(millis() - startTime < 8000){
141 |         reportCount++;
142 |         int16_t x = cos((float)millis() / 1000.0f) * XBOX_STICK_MAX;
143 |         int16_t y = sin((float)millis() / 1000.0f) * XBOX_STICK_MAX;
144 | 
145 |         gamepad->setLeftThumb(x, y);
146 |         gamepad->setRightThumb(x, y);
147 |         gamepad->sendGamepadReport();
148 |         
149 |         if(reportCount % 8 == 0)
150 |             Serial.println("Setting left thumb to " + String(x) + ", " + String(y));
151 |             
152 |         delay(8);
153 |     }
154 | }


--------------------------------------------------------------------------------
/examples/KeyboardExamples/KeyboardLEDs/KeyboardLEDs.ino:
--------------------------------------------------------------------------------
 1 | #include <Arduino.h>
 2 | #include <KeyboardDevice.h>
 3 | #include <BleCompositeHID.h>
 4 | 
 5 | BleCompositeHID compositeHID("ESP32 LED Keyboard", "Mystfit", 100);
 6 | KeyboardDevice* keyboard;
 7 | 
 8 | void OnLEDEvent(KeyboardOutputReport data){
 9 |     Serial.println(
10 |     "LED Report: Number Lock: " + String(data.numLockActive) + 
11 |     " Caps Lock: " + String(data.capsLockActive) + 
12 |     " Scroll Lock: " + String(data.scrollLockActive) + 
13 |     " Compose: " + String(data.composeActive) + 
14 |     " Kana: " + String(data.kanaActive));
15 | }
16 | 
17 | void setup()
18 | {
19 |     Serial.begin(115200);
20 | 
21 |     keyboard = new KeyboardDevice();
22 |     FunctionSlot<KeyboardOutputReport> OnLEDEventSlot(OnLEDEvent);
23 |     keyboard->onLED.attach(OnLEDEventSlot);
24 | 
25 |     compositeHID.addDevice(keyboard);
26 |     compositeHID.begin();
27 | 
28 |     Serial.println("Waiting for connection");
29 |     delay(3000);
30 | }
31 | 
32 | void loop()
33 | {
34 |     keyboard->keyPress(KEY_NUMLOCK);
35 |     delay(10);
36 |     keyboard->keyRelease(KEY_NUMLOCK);
37 |     delay(1000);
38 | 
39 |     keyboard->keyPress(KEY_CAPSLOCK);
40 |     delay(10);
41 |     keyboard->keyRelease(KEY_CAPSLOCK);
42 |     delay(1000);
43 | 
44 |     keyboard->keyPress(KEY_SCROLLLOCK);
45 |     delay(10);
46 |     keyboard->keyRelease(KEY_SCROLLLOCK);
47 |     delay(1000);
48 | 
49 |     keyboard->keyPress(KEY_COMPOSE);
50 |     delay(10);
51 |     keyboard->keyRelease(KEY_COMPOSE);
52 |     delay(1000);
53 | }
54 | 


--------------------------------------------------------------------------------
/examples/KeyboardExamples/MediaKeys/MediaKeys.ino:
--------------------------------------------------------------------------------
 1 | #include <Arduino.h>
 2 | #include <KeyboardDevice.h>
 3 | #include <BleCompositeHID.h>
 4 | 
 5 | 
 6 | BleCompositeHID compositeHID("ESP32 Media Keyboard", "Mystfit", 100);
 7 | KeyboardDevice* keyboard;
 8 | 
 9 | void setup()
10 | {
11 |     Serial.begin(115200);
12 | 
13 |     // Media keys are not enabled by default
14 |     KeyboardConfiguration config;
15 |     config.setUseMediaKeys(true);
16 | 
17 |     keyboard = new KeyboardDevice(config);
18 |     compositeHID.addDevice(keyboard);
19 |     compositeHID.begin();
20 | 
21 |     Serial.println("Waiting for connection");
22 |     delay(3000);
23 | }
24 | 
25 | void pressReleaseMediaKey(uint32_t keyCode){
26 |     Serial.println("Pressing key " + String(keyCode));
27 |     keyboard->mediaKeyPress(keyCode);
28 |     delay(50);
29 |     keyboard->mediaKeyRelease(keyCode);
30 |     delay(1000);
31 | }
32 | 
33 | void loop()
34 | {
35 |     if (compositeHID.isConnected())
36 |     {
37 |         pressReleaseMediaKey(KEY_MEDIA_PLAY);
38 |         pressReleaseMediaKey(KEY_MEDIA_PAUSE);
39 |         pressReleaseMediaKey(KEY_MEDIA_RECORD);
40 |         pressReleaseMediaKey(KEY_MEDIA_FASTFORWARD);
41 |         pressReleaseMediaKey(KEY_MEDIA_REWIND);
42 |         pressReleaseMediaKey(KEY_MEDIA_NEXTTRACK);
43 |         pressReleaseMediaKey(KEY_MEDIA_PREVIOUSTRACK);
44 |         pressReleaseMediaKey(KEY_MEDIA_STOP);
45 |         pressReleaseMediaKey(KEY_MEDIA_EJECT);
46 |         pressReleaseMediaKey(KEY_MEDIA_RANDOMPLAY);
47 |         pressReleaseMediaKey(KEY_MEDIA_REPEAT);
48 |         pressReleaseMediaKey(KEY_MEDIA_PLAYPAUSE);
49 |         pressReleaseMediaKey(KEY_MEDIA_MUTE);
50 |         pressReleaseMediaKey(KEY_MEDIA_VOLUMEUP);
51 |         pressReleaseMediaKey(KEY_MEDIA_VOLUMEDOWN);
52 |         pressReleaseMediaKey(KEY_MEDIA_WWWHOME);
53 |         pressReleaseMediaKey(KEY_MEDIA_MYCOMPUTER);
54 |         pressReleaseMediaKey(KEY_MEDIA_CALCULATOR);
55 |         pressReleaseMediaKey(KEY_MEDIA_WWWFAVORITES);
56 |         pressReleaseMediaKey(KEY_MEDIA_WWWSEARCH);
57 |         pressReleaseMediaKey(KEY_MEDIA_WWWSTOP);
58 |         pressReleaseMediaKey(KEY_MEDIA_WWWBACK);
59 |         pressReleaseMediaKey(KEY_MEDIA_MEDIASELECT);
60 |         pressReleaseMediaKey(KEY_MEDIA_MAIL);
61 |     }
62 | }
63 | 


--------------------------------------------------------------------------------
/examples/KeyboardExamples/PressKey/PressKey.ino:
--------------------------------------------------------------------------------
 1 | #include <Arduino.h>
 2 | #include <KeyboardDevice.h>
 3 | #include <BleCompositeHID.h>
 4 | 
 5 | #define KEY_RANGE_MIN KEY_A
 6 | #define KEY_RANGE_MAX KEY_RIGHTMETA
 7 | 
 8 | #define MODKEY_RANGE_MIN KEY_MOD_LALT
 9 | #define MODKEY_RANGE_MAX KEY_MOD_RMETA
10 | 
11 | BleCompositeHID compositeHID("ESP32 Keyboard", "Mystfit", 100);
12 | KeyboardDevice* keyboard;
13 | 
14 | void setup()
15 | {
16 |     Serial.begin(115200);
17 | 
18 |     keyboard = new KeyboardDevice();
19 |     compositeHID.addDevice(keyboard);
20 |     compositeHID.begin();
21 | 
22 |     Serial.println("Waiting for connection");
23 |     delay(3000);
24 | }
25 | 
26 | void loop()
27 | {
28 |     if (compositeHID.isConnected())
29 |     {
30 |         uint8_t modKey = 0x01;
31 |         for(uint8_t modKeyCount = 0; modKeyCount < 8; modKeyCount++)
32 |         {
33 |             Serial.println("Pressing modifier key " + String(modKey));
34 |             keyboard->modifierKeyPress(modKey);
35 |             delay(10);
36 |             keyboard->modifierKeyRelease(modKey);
37 |             delay(1000);
38 |             modKey = modKey << 1;
39 |         }
40 | 
41 |         for(uint8_t keyCode = KEY_RANGE_MIN; keyCode <= KEY_RANGE_MAX; keyCode++)
42 |         {
43 |             Serial.println("Pressing key " + String(keyCode));
44 |             keyboard->keyPress(keyCode);
45 |             delay(10);
46 |             keyboard->keyRelease(keyCode);
47 |             delay(1000);
48 |         }
49 |     }
50 | }
51 | 


--------------------------------------------------------------------------------
/examples/MultipleDeviceExamples/DeferredReports/DeferredReports.ino:
--------------------------------------------------------------------------------
  1 | // This example demonstrates how device reports can be deferred to be sent later 
  2 | // in order to avoid overloading the BLE connection.
  3 | // You can send queued reports manually by calling the sendDeferredReports() function,
  4 | // or send them regularly by using the setThreadedAutoSend(true) option
  5 | 
  6 | #include <BleCompositeHID.h>
  7 | #include <KeyboardDevice.h>
  8 | #include <MouseDevice.h>
  9 | #include <GamepadDevice.h>
 10 | 
 11 | // Create variables to hold our devices
 12 | GamepadDevice* gamepad = nullptr;
 13 | KeyboardDevice* keyboard = nullptr;
 14 | MouseDevice* mouse = nullptr;
 15 | 
 16 | // Create our composite HID device that will manage our devices
 17 | BleCompositeHID compositeHID("CompositeHID Keyboard Mouse Gamepad", "Mystfit", 100);
 18 | 
 19 | // Variable to track the last time a report was sent
 20 | int lastReportTime = 0;
 21 | 
 22 | void setup() {
 23 |     Serial.begin(115200);
 24 | 
 25 |     // To use deferred reports, we can set the autoDefer flag to true in each device's configuration object
 26 |     // This means that any time we tell a device to send a report, we will queue it instead.
 27 |     // We can then either send all queued reports at once manually, or send them regularly in the background
 28 |     // if we set the autoSendActive flag to true in the BLEHostConfiguration object.
 29 | 
 30 |     // Set up gamepad config
 31 |     GamepadConfiguration gamepadConfig;
 32 |     gamepadConfig.setAutoDefer(true);
 33 | 
 34 |     // Set up gamepad device instance
 35 |     gamepad = new GamepadDevice(gamepadConfig);
 36 |   
 37 |     // Set up keyboard config
 38 |     KeyboardConfiguration keyboardConfig;
 39 |     keyboardConfig.setAutoDefer(true);
 40 | 
 41 |     // Set up keyboard device instance
 42 |     keyboard = new KeyboardDevice(keyboardConfig);
 43 | 
 44 |     // Set up mouse
 45 |     MouseConfiguration mouseConfig;
 46 |     mouseConfig.setAutoDefer(true);
 47 | 
 48 |     // Set up mouse device instance
 49 |     mouse = new MouseDevice(mouseConfig);
 50 | 
 51 |      // Add all devices to the composite HID device to manage them
 52 |     compositeHID.addDevice(keyboard);
 53 |     compositeHID.addDevice(mouse);
 54 |     compositeHID.addDevice(gamepad);
 55 | 
 56 |     // We can also let our composite HID device send reports at a regular interval.
 57 |     // This works by creating a background task that will periodically send all queued reports.
 58 |     BLEHostConfiguration config;
 59 |     config.setQueueSendRate(240);
 60 |     config.setQueuedSending(true);
 61 | 
 62 |     // Start the composite HID device to broadcast HID reports
 63 |     compositeHID.begin(config);
 64 | 
 65 |     // Wait a little bit for the connection to start before sending reports
 66 |     delay(3000);
 67 | }
 68 | 
 69 | void loop() {
 70 |     // Make sure we;re connected to a client before sending any reports
 71 |     if(compositeHID.isConnected())
 72 |     {   
 73 |         // Button states
 74 |         bool gamepadPressed = false;
 75 |         bool keyPressed = false;
 76 |         
 77 |         int currentTime = millis();
 78 |         int elapsedTime = currentTime - lastReportTime;
 79 | 
 80 |         // Test mouse by moving it in a circle
 81 |         mouse->mouseMove(
 82 |             round(cos((float)currentTime / 100.0f) * 10.0f), 
 83 |             round(sin((float)currentTime / 100.0f) * 10.0f)
 84 |         );
 85 | 
 86 |         if(elapsedTime > 1000){
 87 |             // Test keyboard by pressing and releasing the A key every second
 88 |             keyPressed = !keyPressed;
 89 |             if(keyPressed){
 90 |                 keyboard->keyPress(KEY_A);
 91 |             } else {
 92 |                 keyboard->keyRelease(KEY_A);
 93 |             }
 94 |         
 95 |             // Test gamepad by pressing and releasing the A button every second
 96 |             gamepadPressed = !gamepadPressed;
 97 |             if(gamepadPressed){
 98 |                 gamepad->press(BUTTON_1);
 99 |             } else {
100 |                 gamepad->release(BUTTON_1);
101 |             }
102 |         }
103 | 
104 |         // Instead of using the queued send feature, you can call the sendDeferredReports() function
105 |         // to send all queued reports manually. Uncomment the next line to enable.
106 |         //compositeHID.sendDeferredReports();
107 |         
108 |         lastReportTime = currentTime;
109 |         delay(2);
110 |     }
111 | }


--------------------------------------------------------------------------------
/examples/MultipleDeviceExamples/GamepadAndMouse/GamepadAndMouse.ino:
--------------------------------------------------------------------------------
 1 | #include <BleConnectionStatus.h>
 2 | 
 3 | #include <BleCompositeHID.h>
 4 | #include <GamepadDevice.h>
 5 | #include <MouseDevice.h>
 6 | 
 7 | 
 8 | GamepadDevice* gamepad;
 9 | MouseDevice* mouse;
10 | BleCompositeHID compositeHID("CompositeHID Gamepad and Mouse", "Mystfit", 100);
11 | 
12 | void setup() {
13 |   Serial.begin(115200);
14 |   
15 |   // Set up gamepad
16 |   GamepadConfiguration gamepadConfig;
17 |   gamepadConfig.setButtonCount(8);
18 |   gamepadConfig.setHatSwitchCount(0);
19 |   gamepad = new GamepadDevice(gamepadConfig);
20 | 
21 |   // Set up mouse
22 |   mouse = new MouseDevice();
23 | 
24 |   // Add both devices to the composite HID device to manage them
25 |   compositeHID.addDevice(gamepad);
26 |   compositeHID.addDevice(mouse);
27 | 
28 |   // Start the composite HID device to broadcast HID reports
29 |   compositeHID.begin();
30 | }
31 | 
32 | void loop() {
33 |   if(compositeHID.isConnected()){
34 | 
35 |     // Test gamepad
36 |     gamepad->press(BUTTON_3);
37 |     delay(500);               
38 |     gamepad->release(BUTTON_3);
39 |     delay(500);
40 | 
41 |     // Test mouse
42 |     int startTime = millis();
43 |     int reportCount = 0;
44 |     while(millis() - startTime < 8000){
45 |         reportCount++;
46 |         int8_t x = round(cos((float)millis() / 1000.0f) * 10.0f);
47 |         int8_t y = round(sin((float)millis() / 1000.0f) * 10.0f);
48 | 
49 |         mouse->mouseMove(x, y);
50 |         mouse->sendMouseReport();
51 |         
52 |         if(reportCount % 8 == 0)
53 |             Serial.println("Setting relative mouse to " + String(x) + ", " + String(y));
54 |             
55 |         delay(10);
56 |     }
57 |     delay(1000);
58 |   }
59 | }


--------------------------------------------------------------------------------
/examples/MultipleDeviceExamples/GamepadMouseKeyboard/GamepadMouseKeyboard.ino:
--------------------------------------------------------------------------------
 1 | #include <BleConnectionStatus.h>
 2 | 
 3 | #include <BleCompositeHID.h>
 4 | #include <KeyboardDevice.h>
 5 | #include <MouseDevice.h>
 6 | #include <GamepadDevice.h>
 7 | 
 8 | 
 9 | GamepadDevice* gamepad;
10 | KeyboardDevice* keyboard;
11 | MouseDevice* mouse;
12 | BleCompositeHID compositeHID("CompositeHID Keyboard Mouse Gamepad", "Mystfit", 100);
13 | 
14 | void setup() {
15 |     Serial.begin(115200);
16 | 
17 |     // Set up gamepad
18 |     GamepadConfiguration gamepadConfig;
19 |     gamepadConfig.setButtonCount(8);
20 |     gamepadConfig.setHatSwitchCount(0);
21 |     gamepad = new GamepadDevice(gamepadConfig);
22 |   
23 |     // Set up keyboard
24 |     KeyboardConfiguration keyboardConfig;
25 |     keyboard = new KeyboardDevice(keyboardConfig);
26 | 
27 |     // Set up mouse
28 |     MouseConfiguration mouseConfig;
29 |     mouse = new MouseDevice(mouseConfig);
30 | 
31 |      // Add all devices to the composite HID device to manage them
32 |     compositeHID.addDevice(keyboard);
33 |     compositeHID.addDevice(mouse);
34 |     compositeHID.addDevice(gamepad);
35 | 
36 |     // Start the composite HID device to broadcast HID reports
37 |     compositeHID.begin();
38 | 
39 |     delay(3000);
40 | }
41 | 
42 | void loop() {
43 |   if(compositeHID.isConnected()){
44 | 
45 |     // Test mouse
46 |     int startTime = millis();
47 |     int reportCount = 0;
48 | 
49 |     int8_t lastX = 0;
50 |     int8_t lastY = 0;
51 |     bool gamepadPressed = false;
52 | 
53 |     while(millis() - startTime < 8000){
54 |         reportCount++;
55 |         int8_t x = round(cos((float)millis() / 1000.0f) * 10.0f);
56 |         int8_t y = round(sin((float)millis() / 1000.0f) * 10.0f);
57 | 
58 |         // Test mouse
59 |         mouse->mouseMove(x, y);
60 | 
61 |         // Test keyboard
62 |         if(reportCount % 100 == 0){
63 |             keyboard->keyPress(KEY_A);
64 |             keyboard->keyRelease(KEY_A);
65 |         }
66 | 
67 |         // Test gamepad
68 |         if(reportCount % 100 == 0){
69 |             gamepadPressed = !gamepadPressed;
70 |             if(gamepadPressed)
71 |                 gamepad->press(BUTTON_1);
72 |             else
73 |                 gamepad->release(BUTTON_1);
74 |         }
75 |         
76 |         delay(16);
77 |     }
78 |   }
79 | }


--------------------------------------------------------------------------------
/examples/MultipleDeviceExamples/MouseAndKeyboard/MouseAndKeyboard.ino:
--------------------------------------------------------------------------------
 1 | #include <BleConnectionStatus.h>
 2 | 
 3 | #include <BleCompositeHID.h>
 4 | #include <KeyboardDevice.h>
 5 | #include <MouseDevice.h>
 6 | 
 7 | 
 8 | KeyboardDevice* keyboard;
 9 | MouseDevice* mouse;
10 | BleCompositeHID compositeHID("CompositeHID Keyboard and Mouse", "Mystfit", 100);
11 | 
12 | bool pressed = false;
13 | 
14 | void setup() {
15 |     Serial.begin(115200);
16 |   
17 |     // Set up keyboard
18 |     KeyboardConfiguration keyboardConfig;
19 |     keyboardConfig.setAutoReport(false);
20 |     keyboard = new KeyboardDevice(keyboardConfig);
21 | 
22 |     // Set up mouse
23 |     MouseConfiguration mouseConfig;
24 |     mouseConfig.setAutoReport(false);
25 |     mouse = new MouseDevice(mouseConfig);
26 | 
27 |      // Add both devices to the composite HID device to manage them
28 |     compositeHID.addDevice(keyboard);
29 |     compositeHID.addDevice(mouse);
30 | 
31 |     // Start the composite HID device to broadcast HID reports
32 |     compositeHID.begin();
33 | 
34 |     delay(3000);
35 | }
36 | 
37 | void loop() {
38 |   if(compositeHID.isConnected()){
39 | 
40 |     // Test mouse
41 |     int startTime = millis();
42 |     int reportCount = 0;
43 | 
44 |     int8_t lastX = 0;
45 |     int8_t lastY = 0;
46 | 
47 |     while(millis() - startTime < 8000){
48 |         reportCount++;
49 |         int8_t x = round(cos((float)millis() / 1000.0f) * 10.0f);
50 |         int8_t y = round(sin((float)millis() / 1000.0f) * 10.0f);
51 | 
52 |         mouse->mouseMove(x, y);
53 | 
54 |         if(reportCount % 100 == 0){
55 |             // Test keyboard
56 |             keyboard->keyPress(KEY_A);
57 |             keyboard->sendKeyReport();
58 |             keyboard->keyRelease(KEY_A);
59 |             keyboard->sendKeyReport();
60 | 
61 |             if(!pressed){
62 |               mouse->mousePress(MOUSE_LOGICAL_LEFT_BUTTON);
63 |             } else {
64 |               mouse->mouseRelease(MOUSE_LOGICAL_LEFT_BUTTON);
65 |             }
66 |         }
67 |         mouse->sendMouseReport();
68 |         
69 |         delay(16);
70 |     }
71 |   }
72 | }


--------------------------------------------------------------------------------
/examples/SetBatteryLevel/SetBatteryLevel.ino:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * This example shows how to set the battery level to be reported to the host OS
 3 |  * 
 4 |  * It reduces the power by 1% every 30 seconds
 5 |  * 
 6 |  */
 7 | 
 8 | #include <Arduino.h>
 9 | #include <BleCompositeHID.h>
10 | 
11 | BleCompositeHID compositeHID;
12 | 
13 | int batteryLevel = 100;
14 | 
15 | void setup()
16 | {
17 |     Serial.begin(115200);
18 |     Serial.println("Starting BLE work!");
19 |     compositeHID.begin();
20 | }
21 | 
22 | void loop()
23 | {
24 |     if (compositeHID.isConnected())
25 |     {
26 |         if(batteryLevel > 0)
27 |         {
28 |             batteryLevel -= 1;
29 |         }
30 |         
31 |         Serial.print("Battery Level Set To: ");
32 |         Serial.println(batteryLevel);
33 |         compositeHID.setBatteryLevel(batteryLevel);
34 |         delay(30000);
35 |     }
36 | }
37 | 


--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
  1 | #######################################
  2 | # Syntax Coloring Map For ESP32 BLE Gamepad
  3 | #######################################
  4 | # Class
  5 | #######################################
  6 | 
  7 | BleCompositeHID  KEYWORD1
  8 | BleCompositeHIDConfiguration KEYWORD1
  9 | 
 10 | #######################################
 11 | # Methods and Functions
 12 | #######################################
 13 | 
 14 | begin	KEYWORD2
 15 | end	KEYWORD2
 16 | setAxes	KEYWORD2
 17 | press	KEYWORD2
 18 | release	KEYWORD2
 19 | isPressed	KEYWORD2
 20 | isConnected	KEYWORD2
 21 | setLeftThumb KEYWORD2
 22 | setRightThumb	KEYWORD2
 23 | setLeftTrigger KEYWORD2
 24 | setRightTrigger KEYWORD2
 25 | setHat KEYWORD2
 26 | setHat1	KEYWORD2
 27 | setHat2	KEYWORD2
 28 | setHat3	KEYWORD2
 29 | setHat4	KEYWORD2
 30 | setHats	KEYWORD2
 31 | setX KEYWORD2
 32 | setY KEYWORD2
 33 | setZ KEYWORD2
 34 | setRZ KEYWORD2
 35 | setRX KEYWORD2
 36 | setRY KEYWORD2
 37 | setSlider1 KEYWORD2
 38 | setSlider2 KEYWORD2
 39 | setSlider KEYWORD2
 40 | setSliders KEYWORD2
 41 | setRudder	KEYWORD2
 42 | setThrottle	KEYWORD2
 43 | setAccelerator KEYWORD2
 44 | setBrake KEYWORD2
 45 | setSteering KEYWORD2 
 46 | setSimulationControls	KEYWORD2
 47 | setControllerType	KEYWORD2
 48 | setAutoReport KEYWORD2
 49 | setHidReportId KEYWORD2
 50 | sendReport KEYWORD2
 51 | setButtonCount KEYWORD2
 52 | setHatSwitchCount KEYWORD2
 53 | setIncludeStart KEYWORD2
 54 | setIncludeSelect KEYWORD2
 55 | setIncludeMenu KEYWORD2
 56 | setIncludeHome KEYWORD2
 57 | setIncludeBack KEYWORD2
 58 | setIncludeVolumeInc KEYWORD2
 59 | setIncludeVolumeDec KEYWORD2
 60 | setIncludeVolumeMute KEYWORD2
 61 | setWhichSpecialButtons KEYWORD2
 62 | setIncludeXAxis KEYWORD2
 63 | setIncludeYAxis KEYWORD2
 64 | setIncludeZAxis KEYWORD2
 65 | setIncludeRxAxis KEYWORD2
 66 | setIncludeRyAxis KEYWORD2
 67 | setIncludeRzAxis KEYWORD2
 68 | setIncludeSlider1 KEYWORD2
 69 | setIncludeSlider2 KEYWORD2
 70 | setWhichAxes KEYWORD2
 71 | setIncludeRudder KEYWORD2
 72 | setIncludeThrottle KEYWORD2
 73 | setIncludeAccelerator KEYWORD2
 74 | setIncludeBrake KEYWORD2
 75 | setIncludeSteering KEYWORD2
 76 | setWhichSimulationControls KEYWORD2
 77 | resetButtons KEYWORD2
 78 | setBatteryLevel KEYWORD2
 79 | pressSpecialButton KEYWORD2
 80 | releaseSpecialButton KEYWORD2
 81 | pressStart KEYWORD2
 82 | releaseStart KEYWORD2
 83 | pressSelect KEYWORD2
 84 | releaseSelect KEYWORD2
 85 | pressMenu KEYWORD2
 86 | releaseMenu KEYWORD2
 87 | pressHome KEYWORD2
 88 | releaseHome KEYWORD2
 89 | pressBack KEYWORD2
 90 | releaseBack KEYWORD2
 91 | pressVolumeInc KEYWORD2
 92 | releaseVolumeInc KEYWORD2
 93 | pressVolumeDec KEYWORD2
 94 | releaseVolumeDec KEYWORD2
 95 | pressVolumeMute KEYWORD2
 96 | releaseVolumeMute KEYWORD2
 97 | getControllerType KEYWORD2
 98 | getHidReportId KEYWORD2
 99 | getButtonCount KEYWORD2
100 | getTotalSpecialButtonCount KEYWORD2
101 | getDesktopSpecialButtonCount KEYWORD2
102 | getConsumerSpecialButtonCount KEYWORD2
103 | getHatSwitchCount KEYWORD2
104 | getAxisCount KEYWORD2
105 | getSimulationCount KEYWORD2
106 | getAutoReport KEYWORD2
107 | getIncludeStart KEYWORD2
108 | getIncludeSelect KEYWORD2
109 | getIncludeMenu KEYWORD2
110 | getIncludeHome KEYWORD2
111 | getIncludeBack KEYWORD2
112 | getIncludeVolumeInc KEYWORD2
113 | getIncludeVolumeDec KEYWORD2
114 | getIncludeVolumeMute KEYWORD2
115 | getWhichSpecialButtons KEYWORD2
116 | getIncludeXAxis KEYWORD2
117 | getIncludeYAxis KEYWORD2
118 | getIncludeZAxis KEYWORD2
119 | getIncludeRxAxis KEYWORD2
120 | getIncludeRyAxis KEYWORD2
121 | getIncludeRzAxis KEYWORD2
122 | getIncludeSlider1 KEYWORD2
123 | getIncludeSlider2 KEYWORD2
124 | getWhichAxes KEYWORD2
125 | getIncludeRudder KEYWORD2
126 | getIncludeThrottle KEYWORD2
127 | getIncludeAccelerator KEYWORD2
128 | getIncludeBrake KEYWORD2
129 | getIncludeSteering KEYWORD2
130 | getWhichSimulationControls KEYWORD2
131 | getVid KEYWORD2
132 | getPid KEYWORD2
133 | getGuidVersion KEYWORD2
134 | getAxesMin KEYWORD2
135 | getAxesMax KEYWORD2
136 | getSimulationMin KEYWORD2
137 | getSimulationMax KEYWORD2
138 | getModelNumber KEYWORD2
139 | getSoftwareRevision KEYWORD2
140 | getSerialNumber KEYWORD2
141 | getFirmwareRevision KEYWORD2
142 | getHardwareRevision KEYWORD2
143 | setVid KEYWORD2
144 | setPid KEYWORD2
145 | setGuidVersion KEYWORD2
146 | setAxesMin KEYWORD2
147 | setAxesMax KEYWORD2
148 | setSimulationMin KEYWORD2
149 | setSimulationMax KEYWORD2
150 | setModelNumber KEYWORD2
151 | setSoftwareRevision KEYWORD2
152 | setSerialNumber KEYWORD2
153 | setFirmwareRevision KEYWORD2
154 | setHardwareRevision KEYWORD2
155 | 
156 | 
157 | #######################################
158 | # Constants
159 | #######################################
160 | 
161 | CONTROLLER_TYPE_JOYSTICK LITERAL1
162 | CONTROLLER_TYPE_GAMEPAD LITERAL1
163 | CONTROLLER_TYPE_MULTI_AXIS LITERAL1
164 | 
165 | BUTTON_1 LITERAL1
166 | BUTTON_2 LITERAL1
167 | BUTTON_3 LITERAL1
168 | BUTTON_4 LITERAL1
169 | BUTTON_5 LITERAL1
170 | BUTTON_6 LITERAL1
171 | BUTTON_7 LITERAL1
172 | BUTTON_8 LITERAL1
173 | BUTTON_9 LITERAL1
174 | BUTTON_10 LITERAL1
175 | BUTTON_11 LITERAL1
176 | BUTTON_12 LITERAL1
177 | BUTTON_13 LITERAL1
178 | BUTTON_14 LITERAL1
179 | BUTTON_15 LITERAL1
180 | BUTTON_16 LITERAL1
181 | BUTTON_17 LITERAL1
182 | BUTTON_18 LITERAL1
183 | BUTTON_19 LITERAL1
184 | BUTTON_20 LITERAL1
185 | BUTTON_21 LITERAL1
186 | BUTTON_22 LITERAL1
187 | BUTTON_23 LITERAL1
188 | BUTTON_24 LITERAL1
189 | BUTTON_25 LITERAL1
190 | BUTTON_26 LITERAL1
191 | BUTTON_27 LITERAL1
192 | BUTTON_28 LITERAL1
193 | BUTTON_29 LITERAL1
194 | BUTTON_30 LITERAL1
195 | BUTTON_31 LITERAL1
196 | BUTTON_32 LITERAL1
197 | BUTTON_33 LITERAL1
198 | BUTTON_34 LITERAL1
199 | BUTTON_35 LITERAL1
200 | BUTTON_36 LITERAL1
201 | BUTTON_37 LITERAL1
202 | BUTTON_38 LITERAL1
203 | BUTTON_39 LITERAL1
204 | BUTTON_40 LITERAL1
205 | BUTTON_41 LITERAL1
206 | BUTTON_42 LITERAL1
207 | BUTTON_43 LITERAL1
208 | BUTTON_44 LITERAL1
209 | BUTTON_45 LITERAL1
210 | BUTTON_46 LITERAL1
211 | BUTTON_47 LITERAL1
212 | BUTTON_48 LITERAL1
213 | BUTTON_49 LITERAL1
214 | BUTTON_50 LITERAL1
215 | BUTTON_51 LITERAL1
216 | BUTTON_52 LITERAL1
217 | BUTTON_53 LITERAL1
218 | BUTTON_54 LITERAL1
219 | BUTTON_55 LITERAL1
220 | BUTTON_56 LITERAL1
221 | BUTTON_57 LITERAL1
222 | BUTTON_58 LITERAL1
223 | BUTTON_59 LITERAL1
224 | BUTTON_60 LITERAL1
225 | BUTTON_61 LITERAL1
226 | BUTTON_62 LITERAL1
227 | BUTTON_63 LITERAL1
228 | BUTTON_64 LITERAL1
229 | BUTTON_65 LITERAL1
230 | BUTTON_66 LITERAL1
231 | BUTTON_67 LITERAL1
232 | BUTTON_68 LITERAL1
233 | BUTTON_69 LITERAL1
234 | BUTTON_70 LITERAL1
235 | BUTTON_71 LITERAL1
236 | BUTTON_72 LITERAL1
237 | BUTTON_73 LITERAL1
238 | BUTTON_74 LITERAL1
239 | BUTTON_75 LITERAL1
240 | BUTTON_76 LITERAL1
241 | BUTTON_77 LITERAL1
242 | BUTTON_78 LITERAL1
243 | BUTTON_79 LITERAL1
244 | BUTTON_80 LITERAL1
245 | BUTTON_81 LITERAL1
246 | BUTTON_82 LITERAL1
247 | BUTTON_83 LITERAL1
248 | BUTTON_84 LITERAL1
249 | BUTTON_85 LITERAL1
250 | BUTTON_86 LITERAL1
251 | BUTTON_87 LITERAL1
252 | BUTTON_88 LITERAL1
253 | BUTTON_89 LITERAL1
254 | BUTTON_90 LITERAL1
255 | BUTTON_91 LITERAL1
256 | BUTTON_92 LITERAL1
257 | BUTTON_93 LITERAL1
258 | BUTTON_94 LITERAL1
259 | BUTTON_95 LITERAL1
260 | BUTTON_96 LITERAL1
261 | BUTTON_97 LITERAL1
262 | BUTTON_98 LITERAL1
263 | BUTTON_99 LITERAL1
264 | BUTTON_100 LITERAL1
265 | BUTTON_101 LITERAL1
266 | BUTTON_102 LITERAL1
267 | BUTTON_103 LITERAL1
268 | BUTTON_104 LITERAL1
269 | BUTTON_105 LITERAL1
270 | BUTTON_106 LITERAL1
271 | BUTTON_107 LITERAL1
272 | BUTTON_108 LITERAL1
273 | BUTTON_109 LITERAL1
274 | BUTTON_110 LITERAL1
275 | BUTTON_111 LITERAL1
276 | BUTTON_112 LITERAL1
277 | BUTTON_113 LITERAL1
278 | BUTTON_114 LITERAL1
279 | BUTTON_115 LITERAL1
280 | BUTTON_116 LITERAL1
281 | BUTTON_117 LITERAL1
282 | BUTTON_118 LITERAL1
283 | BUTTON_119 LITERAL1
284 | BUTTON_120 LITERAL1
285 | BUTTON_121 LITERAL1
286 | BUTTON_122 LITERAL1
287 | BUTTON_123 LITERAL1
288 | BUTTON_124 LITERAL1
289 | BUTTON_125 LITERAL1
290 | BUTTON_126 LITERAL1
291 | BUTTON_127 LITERAL1
292 | BUTTON_128 LITERAL1
293 | 
294 | DPAD_CENTERED LITERAL1
295 | DPAD_UP LITERAL1
296 | DPAD_UP_RIGHT LITERAL1
297 | DPAD_RIGHT LITERAL1
298 | DPAD_DOWN_RIGHT LITERAL1
299 | DPAD_DOWN LITERAL1
300 | DPAD_DOWN_LEFT LITERAL1
301 | DPAD_LEFT LITERAL1
302 | DPAD_UP_LEFT LITERAL1
303 | 
304 | HAT_CENTERED LITERAL1
305 | HAT_UP LITERAL1
306 | HAT_UP_RIGHT LITERAL1
307 | HAT_RIGHT LITERAL1
308 | HAT_DOWN_RIGHT LITERAL1
309 | HAT_DOWN LITERAL1
310 | HAT_DOWN_LEFT LITERAL1
311 | HAT_LEFT LITERAL1
312 | HAT_UP_LEFT LITERAL1
313 | 


--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
 1 | name=ESP32-BLE-CompositeHID
 2 | version=0.3.0
 3 | author=Mystfit
 4 | maintainer=Mystfit
 5 | sentence=Bluetooth LE Composite HID device library for the ESP32.
 6 | paragraph=Bluetooth LE Composite HID device library for the ESP32. Combines a BLE mouse, gamepad and keyboard into a single composite HID device. Forked from https://github.com/lemmingDev/ESP32-BLE-Gamepad
 7 | category=Communication
 8 | url=https://github.com/mystfit/ESP32-BLE-CompositeHID
 9 | architectures=esp32
10 | depends=NimBLE-Arduino,Callback


--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
 1 | Software License Agreement (MIT License)
 2 | 
 3 | Copyright (c) 2021 lemmingDev - https://github.com/lemmingDev
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.


--------------------------------------------------------------------------------
/notes/packet_logs.txt:
--------------------------------------------------------------------------------
 1 | // Included in this file are two sample packets captured via Wireshark.
 2 | // The first is captured from a latest model XBox Series X wireless controller
 3 | 
 4 | // Xbox wireless controller 1914 - pressing right trigger
 5 | char packet_bytes_controller[] = {
 6 |   0x01, // Report ID
 7 |   0x22, // ?
 8 |   0x17, // Size (23 bytes)
 9 |   0x00, // Seperator before size is counted
10 |   
11 |   // Counting from here onwards there are 23 bytes
12 |   0x13, 0x00, 0x04, 0x00, 0x1b, 0x1e, 0x00, //unknown?
13 |   0xa6, 0x81, // X axis
14 |   0x1d, 0x7c, // Y axis
15 |   0x4c, 0x81, // Z axis
16 |   0xe8, 0x81, // rZ axis
17 |   0x00, 0x00, // Brake (Left trigger)
18 |   0xa0, 0x01, // Accelerator (Right trigger)
19 |   0x00,       // Hat switch
20 |   0x00, 0x00, // Button bitflags
21 |   0x00        // Share button
22 | };
23 | 
24 | // The second is a packet from our ESP32 controller
25 | // ESP32
26 | 
27 | char packet_bytes_esp32[] = {
28 |   0x01, // Report ID
29 |   0x22, // ?
30 |   0x17, // Size (23 bytes)
31 |   0x00, // Size seperator
32 |   // Counting from here onwards there are 23 bytes
33 | 
34 |   0x13, 0x00, 0x04, 0x00, 0x1b, //unknown but identical to a real controller
35 |   0x23, 0x00, // Unknown and differs from a normal controller but still seems to work
36 | 
37 |   0x00, 0x00, // X axis
38 |   0x00, 0x00, // Y axis
39 |   0x00, 0x00, // Z axis
40 |   0x00, 0x00, // rZ axis
41 |   0x16, 0x03, // Brake (Left trigger)
42 |   0x15, 0x03, // Accelerator (Right trigger)
43 |   0x00,       // Hat switch
44 |   0x00, 0x00, // Button bitflags
45 |   0x00        // Share button
46 | };
47 | 


--------------------------------------------------------------------------------
/scripts/parse-descriptor.py:
--------------------------------------------------------------------------------
 1 | import re
 2 | import sys
 3 | 
 4 | def reformat_text(input_text):
 5 |     # Regular expression to match lines with hexadecimal numbers
 6 |     pattern = re.compile(r'([0-9a-fA-F]+)\s*([0-9a-fA-F]+)?\s*(.*)$', re.MULTILINE)
 7 | 
 8 |     # Find all matches in the input text
 9 |     matches = pattern.findall(input_text)
10 | 
11 |     # Reformat each match
12 |     reformatted_lines = []
13 |     for match in matches:
14 |         hex_numbers = [f'0x{num},' for num in filter(None, [match[0], match[1]])]  # Filter out None values
15 | 
16 |         comment_part = f' //{match[2].rstrip()}' if match[2] else ''  # Preserve whitespace in the comment
17 | 
18 |         reformatted_line = ' '.join(hex_numbers) + comment_part
19 |         reformatted_lines.append(reformatted_line)
20 | 
21 |     # Join the reformatted lines into a single string
22 |     output_text = '\n'.join(reformatted_lines)
23 | 
24 |     return output_text
25 | 
26 | if __name__ == "__main__":
27 |     # Read input text from stdin
28 |     input_text = sys.stdin.read()
29 | 
30 |     # Reformat the text
31 |     output_text = reformat_text(input_text)
32 | 
33 |     # Print the reformatted text
34 |     print(output_text)
35 |
```