Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bluetooth connect to HID keyboard example fails #50

Open
goedzo opened this issue Sep 24, 2024 · 1 comment
Open

Bluetooth connect to HID keyboard example fails #50

goedzo opened this issue Sep 24, 2024 · 1 comment

Comments

@goedzo
Copy link

goedzo commented Sep 24, 2024

I am trying to connect a bluetooth HID keyboard to my t-echo, however the example code https://github.com/Xinyuan-LilyGO/T-Echo/tree/main/examples/ble_hid_central does not work.

Even the comment in the code itself says this:
display->print("Scan ble keyboard"); //crashes here

So how can I connect my bluetooth keyboard to my t-echo device?

@goedzo
Copy link
Author

goedzo commented Sep 24, 2024

This is the code I use to find my keyboard, but I never see any keyboard device:

#include <bluefruit.h>

#define CONNECTION_TIMEOUT_MS 5000  // 5 seconds timeout for connection attempts
#define heartbeat_TIMEOUT_MS 5000  //

BLEClientDis disClient; // GATT client for Device Information Service (DIS)

uint32_t lastConnectionAttempt = 0;  // Time of the last connection attempt
uint32_t heartbeat = 0;  // Time of the last connection attempt
bool isConnecting = false;           // Flag to track if we are attempting to connect
uint16_t connHandle = BLE_CONN_HANDLE_INVALID;  // Connection handle to track the current connection

// List to store MAC addresses of devices that have been tried (as strings)
const int MAX_TRIED_DEVICES = 50;  // Allow more devices to be tried
String triedDevices[MAX_TRIED_DEVICES];
int triedDeviceCount = 0;

String currentDeviceAddr;  // Store current device address during connection attempt

// Helper function to convert a MAC address to a string
String addressToString(uint8_t* addr) {
  char str[18];  // MAC address as string "XX:XX:XX:XX:XX:XX"
  snprintf(str, sizeof(str), "%02X:%02X:%02X:%02X:%02X:%02X",
           addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
  return String(str);
}

bool isDeviceInTriedList(String addr) {
  for (int i = 0; i < triedDeviceCount; i++) {
    if (triedDevices[i] == addr) {
      return true;  // Device already tried
    }
  }
  return false;
}

void addDeviceToTriedList(String addr) {
  if (triedDeviceCount < MAX_TRIED_DEVICES) {
    triedDevices[triedDeviceCount] = addr;  // Add the address as a string
    triedDeviceCount++;
  }
}

// Callback function when a connection is established
void connect_callback(uint16_t conn_handle) {
  BLEConnection* conn = Bluefruit.Connection(conn_handle);

  isConnecting = false;
  connHandle = conn_handle;  // Store the connection handle

  // Discover GATT Services (including Device Information Service)
  if (disClient.discover(conn_handle)) {
    char buffer[32] = {0};  // Buffer to store characteristic values

    // Query the manufacturer name
    if (disClient.getManufacturer(buffer, sizeof(buffer))) {
      Serial.print("Manufacturer: ");
      Serial.println(buffer);
    }

    // Query the model number
    if (disClient.getModel(buffer, sizeof(buffer))) {
      Serial.print("Model: ");
      Serial.println(buffer);
    }

    // Query the serial number
    if (disClient.getSerial(buffer, sizeof(buffer))) {
      Serial.print("Serial Number: ");
      Serial.println(buffer);
    }

    // Query the firmware revision
    if (disClient.getFirmwareRev(buffer, sizeof(buffer))) {
      Serial.print("Firmware Version: ");
      Serial.println(buffer);
    }

    // Query the hardware revision
    if (disClient.getHardwareRev(buffer, sizeof(buffer))) {
      Serial.print("Hardware Version: ");
      Serial.println(buffer);
    }

    // Query the software revision (if available)
    if (disClient.getSoftwareRev(buffer, sizeof(buffer))) {
      Serial.print("Software Version: ");
      Serial.println(buffer);
    }

  } else {
    Serial.println("Device Information Service NOT found");
  }

  // Disconnect after retrieving the information
  conn->disconnect();
}

// Callback function when a connection is dropped or disconnected
void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
  connHandle = BLE_CONN_HANDLE_INVALID;  // Reset the connection handle
  isConnecting = false;
  // Scanning will automatically resume due to `restartOnDisconnect`
}

void parseAppleManufacturerData(uint8_t* data, int len) {
  if (len >= 25) {  // iBeacon packets are at least 25 bytes long
    uint16_t companyId = (data[1] << 8) | data[0];
    if (companyId == 0x004C) {  // Apple Inc.
      uint8_t beaconType = data[2];
      if (beaconType == 0x02 && data[3] == 0x15) {  // iBeacon type
        Serial.print("iBeacon UUID: ");
        for (int i = 4; i < 20; i++) {
          Serial.printf("%02X", data[i]);
          if (i == 7 || i == 9 || i == 11 || i == 13) Serial.print("-");
        }
        Serial.println();
        int major = (data[20] << 8) | data[21];
        int minor = (data[22] << 8) | data[23];
        int8_t txPower = data[24];

        Serial.printf("Major: %d, Minor: %d, Tx Power: %d dBm\n", major, minor, txPower);
      }
    }
  }
}


void scan_callback(ble_gap_evt_adv_report_t* report) {
  // Stop scanning before connecting
  Bluefruit.Scanner.stop();

  // Convert the device address to a string
  currentDeviceAddr = addressToString(report->peer_addr.addr);

  // Check if the device is in the tried list before proceeding
  if (isDeviceInTriedList(currentDeviceAddr)) {
    Bluefruit.Scanner.start(0);  // Resume scanning if the device has already been tried
    return;  // Exit if the device is in the tried list
  }

  // Add the device to the tried list
  addDeviceToTriedList(currentDeviceAddr);

  // Print new device found
  Serial.printf("New Device: %s\n", currentDeviceAddr.c_str());

  // Compact Advertising Report Type Details
  Serial.printf("Connectable: %d, Scannable: %d, Directed: %d, Scan Resp: %d, Ext PDU: %d, Data Status: %d\n",
                report->type.connectable, report->type.scannable, report->type.directed,
                report->type.scan_response, report->type.extended_pdu, report->type.status);

  // Skip non-connectable devices
  if (report->type.connectable == 0) {
    Serial.println("Skipping non-connectable device.");
    Bluefruit.Scanner.start(0);  // Resume scanning
    return;
  }

  // Check for Appearance (if available)
  uint8_t buffer[2] = {0};
  if (Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_APPEARANCE, buffer, sizeof(buffer))) {
    uint16_t appearance = buffer[1] << 8 | buffer[0];  // Combine the two bytes
    Serial.printf("Appearance: 0x%04X\n", appearance);
    printAppearance(appearance);  // Custom function to print human-readable appearance
  } else {
    Serial.println("Appearance: Not available");
  }

  // Check for Manufacturer Specific Data (if available)
  uint8_t manuData[32] = {0};
  int manuDataLen = Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA, manuData, sizeof(manuData));
  if (manuDataLen > 0) {
    Serial.print("Manufacturer Data: ");
    for (int i = 0; i < manuDataLen; i++) {
      Serial.printf("%02X ", manuData[i]);
    }
    Serial.println();

    // If Apple Manufacturer Data, try to parse it further (e.g., iBeacon data)
    if (manuData[0] == 0x4C && manuData[1] == 0x00) {
      parseAppleManufacturerData(manuData, manuDataLen);  // Custom function to parse Apple data
    }
  } else {
    Serial.println("Manufacturer Data: Not available");
  }

  // Check for TX Power Level (if available)
  uint8_t txPower[1] = {0};
  if (Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_TX_POWER_LEVEL, txPower, sizeof(txPower))) {
    Serial.printf("TX Power Level: %d dBm\n", (int8_t)txPower[0]);
  } else {
    Serial.println("TX Power Level: Not available");
  }

  // Check for common GATT services to identify the device type
  if (Bluefruit.Scanner.checkReportForUuid(report, 0x1812)) {
    Serial.println("Service: HID (Keyboard/Mouse/Gamepad)");
  } else if (Bluefruit.Scanner.checkReportForUuid(report, 0x180F)) {
    Serial.println("Service: Battery Service");
  } else if (Bluefruit.Scanner.checkReportForUuid(report, 0x180E)) {
    Serial.println("Service: Phone Alert Status");
  } else {
    Serial.println("Service: Unknown or Not Advertised");
  }

  // Try to connect if no name is advertised
  uint8_t nameBuffer[32] = {0};
  if (!Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, nameBuffer, sizeof(nameBuffer)) &&
      !Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME, nameBuffer, sizeof(nameBuffer))) {
    
    // No name advertised, attempting to connect
    if (!isConnecting) {
      lastConnectionAttempt = millis();
      isConnecting = true;

      // Try to connect to the device
      Bluefruit.Central.connect(report);
    }
  } else {
    Serial.printf("Advertised Name: %s\n", nameBuffer);
  }

  Bluefruit.Scanner.start(0);  // Resume scanning
}


// Helper function to print human-readable appearance
void printAppearance(uint16_t appearance) {
  switch (appearance) {
    case 961:
      Serial.println("Device Type: Keyboard");
      break;
    case 962:
      Serial.println("Device Type: Mouse");
      break;
    case 963:
      Serial.println("Device Type: Gamepad");
      break;
    case 1344:
      Serial.println("Device Type: Phone");
      break;
    case 832:
      Serial.println("Device Type: Watch");
      break;
    default:
      Serial.println("Device Type: Unknown");
      break;
  }
}


void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);

  // Initialize Bluefruit with max connections (Peripheral = 0, Central = 1)
  Bluefruit.begin(0, 1);

  // Set name for this BLE Central device
  Bluefruit.setName("Bluefruit52 Central");

  // Set connection and disconnection callbacks
  Bluefruit.Central.setConnectCallback(connect_callback);
  Bluefruit.Central.setDisconnectCallback(disconnect_callback);

  // Automatically restart scanning on disconnection
  Bluefruit.Scanner.restartOnDisconnect(true);  // Automatically restart scanning after disconnect

  // Initialize the Device Information Service client
  disClient.begin();

  // Start scanning for devices
  Bluefruit.Scanner.setRxCallback(scan_callback);
  Bluefruit.Scanner.setInterval(160, 80);       // Set scan interval and window
  Bluefruit.Scanner.useActiveScan(true);        // Request scan response data
  Bluefruit.Scanner.start(0);                   // 0 = scan forever
}

void loop() {
  if(millis() - heartbeat > heartbeat_TIMEOUT_MS ){
      Serial.printf(".");
      heartbeat=millis();
  }

  // Check for timeout on connection attempts
  if (isConnecting && (millis() - lastConnectionAttempt > CONNECTION_TIMEOUT_MS)) {
    // Forcefully stop the connection attempt by resetting BLE Central
    if (connHandle != BLE_CONN_HANDLE_INVALID) {
      BLEConnection* conn = Bluefruit.Connection(connHandle);
      if (conn) {
        conn->disconnect();  // Properly disconnect the active connection
      }
    }

    // Reset connection-related variables
    isConnecting = false;
    connHandle = BLE_CONN_HANDLE_INVALID;

    // Restart scanning
    Bluefruit.Scanner.stop();
    delay(100);  // Short delay to ensure the stop completes
    Bluefruit.Scanner.start(0);  // Restart scanning
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant