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

pScan->start(...) blocking forever #617

Closed
Dario-Ciceri opened this issue Dec 15, 2023 · 7 comments
Closed

pScan->start(...) blocking forever #617

Dario-Ciceri opened this issue Dec 15, 2023 · 7 comments

Comments

@Dario-Ciceri
Copy link

Dario-Ciceri commented Dec 15, 2023

Hi, when I run pScan->start(5, scanEndedCB, false); or pScan->start(5, false); even after it has finished scanning the program goes into infinite loop and I don't understand why, my code doesn't include cycles after the call to start (apart from the loop of course) and it just seems like the program is waiting for something and doesn't even crash, I hope you can provide me with a solution, thanks in advance.

update: only occurs if the bluetooth device I want to connect to with esp32 is turned off (a BLE remote control)

@Dario-Ciceri
Copy link
Author

maybe I found the problem, the scan doesn't stop even after the endscancb is called

@Dario-Ciceri
Copy link
Author

nope, even by adding in the callback NimBLEDevice::getScan()->stop(); it doesn't work and keep waiting for something

16:15:50.979 -> Scan Ended
16:15:50.979 -> D NimBLEScan: >> stop()
16:15:50.979 -> D NimBLEScan: << stop()

@Dario-Ciceri
Copy link
Author

this #297 helps but it's not perfect btw

@h2zero
Copy link
Owner

h2zero commented Dec 15, 2023

What MCU is this on? Can you share a code snippet to reproduce?

@Dario-Ciceri
Copy link
Author

Dario-Ciceri commented Dec 17, 2023

What MCU is this on? Can you share a code snippet to reproduce?

I'm using an esp32, I fixed it by using a task on core 0.

What I don't understand is why both the blocking and non-blocking versions, after scanning for "x" seconds, don't return control to the main loop, obviously with two cores there are no problems for the application I need to run however I would like to be able to solve or at least understand if I am doing something wrong.

Part of the code:

//(NOT USED) quick fix blocking scan https://github.com/h2zero/NimBLE-Arduino/issues/297
#include <NimBLEDevice.h>
#include <vector>
using namespace std;

TaskHandle_t scanTask;
bool scanTaskRunning = false;

#define ServerName "VR-PARK"

NimBLEAddress ServerAddress = 0x48472e390db3;

void scanEndedCB(NimBLEScanResults results);

// UUID HID
static NimBLEUUID serviceUUID("1812");
// UUID Report Charcteristic
static NimBLEUUID charUUID("2a4d");

static NimBLEAdvertisedDevice *advDevice;

static bool doConnect = false;
static bool clientConnected = false;
static uint32_t scanTime = 0; /** 0 = scan forever */

class ClientCallbacks : public NimBLEClientCallbacks {
    void onConnect(NimBLEClient *pClient) {
      Serial.println("Connected");
      clientConnected = true;
      /** After connection we should change the parameters if we don't need fast response times.
            These settings are 150ms interval, 0 latency, 450ms timout.
            Timeout should be a multiple of the interval, minimum is 100ms.
            I find a multiple of 3-5 * the interval works best for quick response/reconnect.
            Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout
      */
      pClient->updateConnParams(120, 120, 0, 1);
    };

    void onDisconnect(NimBLEClient *pClient) {
      Serial.print(pClient->getPeerAddress().toString().c_str());
      Serial.println(" Disconnected - Starting scan");
      clientConnected = false;
      //NimBLEDevice::getScan()->start(scanTime, false);
    };

    /** Called when the peripheral requests a change to the connection parameters.
          Return true to accept and apply them or false to reject and keep
          the currently used parameters. Default will return true.
    */
    bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) {
      if (params->itvl_min < 24) { /** 1.25ms units */
        return false;
      } else if (params->itvl_max > 40) { /** 1.25ms units */
        return false;
      } else if (params->latency > 2) { /** Number of intervals allowed to skip */
        return false;
      } else if (params->supervision_timeout > 100) { /** 10ms units */
        return false;
      }

      return true;
    };

    /********************* Security handled here **********************
       ****** Note: these are the same return values as defaults ********/
    uint32_t onPassKeyRequest() {
      //Serial.println("Client Passkey Request");
      /** return the passkey to send to the server */
      return 123456;
    };

    bool onConfirmPIN(uint32_t pass_key) {
      //Serial.print("The passkey YES/NO number: ");
      //Serial.println(pass_key);
      /** Return false if passkeys don't match. */
      return true;
    };

    /** Pairing proces\s complete, we can check the results in ble_gap_conn_desc */
    void onAuthenticationComplete(ble_gap_conn_desc *desc) {
      if (!desc->sec_state.encrypted) {
        //Serial.println("Encrypt connection failed - disconnecting");
        /** Find the client with the connection handle provided in desc */
        NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
        return;
      }
    };
};

/** Define a class to handle the callbacks when advertisments are received */
class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {

    void onResult(NimBLEAdvertisedDevice *advertisedDevice) {
      //Serial.print("Advertised Device found: ");
      //        ////Serial.println(advertisedDevice->toString().c_str());
      //Serial.printf("name:%s, address:%s ", advertisedDevice->getName().c_str(), advertisedDevice->getAddress().toString().c_str());
      //Serial.printf("UUID:%s\n", advertisedDevice->haveServiceUUID() ? advertisedDevice->getServiceUUID().toString().c_str() : "none");

      if (advertisedDevice->isAdvertisingService(serviceUUID) && advertisedDevice->getAddress().equals(ServerAddress)) {
        //Serial.println("Found Our Service");
        /** stop scan before connecting */
        NimBLEDevice::getScan()->stop();
        /** Save the device reference in a global for the client to use*/
        advDevice = advertisedDevice;
        /** Ready to connect now */
        doConnect = true;
      }
    };
};

int lastJoystickData = -1;

/** Notification / Indication receiving handler callback */
void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
  std::string str = (isNotify == true) ? "Notification" : "Indication";
  str += " from ";
  str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString();
  str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
  str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
  //    str += ", Value = " + std::string((char*)pData, length);
  //////Serial.println(str.c_str());
  /*////Serial.println("data: ");
    for (int i = 0; i < length; i++) {
    // uint8_tを頭0のstringで表示する
    ////Serial.println("i" + String(i) + ": " + pData[i]);
    }
    ////Serial.print("\nlength: ");
    ////Serial.print(length);
    ////Serial.println();*/

  //lastJoystickData = -1;

  if (pData[0] == 8) {
    Serial.println("LB");
  }
  if (pData[0] == 1) {
    Serial.println("UB");
  }
  if (pData[0] == 233) {
    Serial.println("C");
  }
  if (pData[0] == 234) {
    Serial.println("D");
  }
  if (pData[2] == 239) {
    Serial.println("JU");
  }
  if (pData[2] == 15) {
    Serial.println("JD");
  }
  if (pData[1] == 239) {
    Serial.println("JL");
  }
  if (pData[1] == 15) {
    Serial.println("JR");
  }
  if (pData[0] == 0 && pData[1] == 0 && pData[2] == 0 && pData[3] == 0) {
    Serial.println("S");
  }
}

/** Callback to process the results of the last scan or restart it */
void scanEndedCB(NimBLEScanResults results) {
  Serial.println("Scan Ended");
  scanTaskRunning = false;
}

/** Create a single global instance of the callback class to be used by all clients */
static ClientCallbacks clientCB;

/** Handles the provisioning of clients and connects / interfaces with the server */
bool connectToServer() {
  NimBLEClient *pClient = nullptr;

  /** Check if we have a client we should reuse first **/
  if (NimBLEDevice::getClientListSize()) {
    /** Special case when we already know this device, we send false as the
        second argument in connect() to prevent refreshing the service database.
        This saves considerable time and power.
    */
    pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress());
    if (pClient) {
      if (!pClient->connect(advDevice, false)) {
        //Serial.println("Reconnect failed");
        return false;
      }
      //Serial.println("Reconnected client");
    }
    /** We don't already have a client that knows this device,
        we will check for a client that is disconnected that we can use.
    */
    else {
      pClient = NimBLEDevice::getDisconnectedClient();
    }
  }

  /** No client to reuse? Create a new one. */
  if (!pClient) {
    if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
      //Serial.println("Max clients reached - no more connections available");
      return false;
    }

    pClient = NimBLEDevice::createClient();

    //Serial.println("New client created");

    pClient->setClientCallbacks(&clientCB, false);
    /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
        These settings are safe for 3 clients to connect reliably, can go faster if you have less
        connections. Timeout should be a multiple of the interval, minimum is 100ms.
        Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
    */
    pClient->setConnectionParams(12, 12, 0, 51);
    /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
    pClient->setConnectTimeout(5);

    if (!pClient->connect(advDevice)) {
      /** Created a client but failed to connect, don't need to keep it as it has no data */
      NimBLEDevice::deleteClient(pClient);
      //Serial.println("Failed to connect, deleted client");
      return false;
    }
  }

  if (!pClient->isConnected()) {
    if (!pClient->connect(advDevice)) {
      //Serial.println("Failed to connect");
      return false;
    }
  }

  //Serial.print("Connected to: ");
  //Serial.println(pClient->getPeerAddress().toString().c_str());
  //Serial.print("RSSI: ");
  //Serial.println(pClient->getRssi());

  /** Now we can read/write/subscribe the charateristics of the services we are interested in */
  NimBLERemoteService *pSvc = nullptr;
  //  NimBLERemoteCharacteristic *pChr = nullptr;
  std::vector<NimBLERemoteCharacteristic *> *pChrs = nullptr;

  NimBLERemoteDescriptor *pDsc = nullptr;

  pSvc = pClient->getService(serviceUUID);
  if (pSvc) { /** make sure it's not null */
    pChrs = pSvc->getCharacteristics(true);
  }

  if (pChrs) { /** make sure it's not null */

    for (int i = 0; i < pChrs->size(); i++) {

      if (pChrs->at(i)->canNotify()) {
        /** Must send a callback to subscribe, if nullptr it will unsubscribe */
        if (!pChrs->at(i)->registerForNotify(notifyCB)) {
          /** Disconnect if subscribe failed */
          pClient->disconnect();
          return false;
        }
      }
    }
  }

  else {
    //Serial.println("DEAD service not found.");
  }

  //Serial.println("Done with this device!");
  return true;
}

void scanTaskFunction(void *pvParameters) {
  scanTaskRunning = true;

  /** Set the IO capabilities of the device, each option will trigger a different pairing method.
      BLE_HS_IO_KEYBOARD_ONLY    - Passkey pairing
      BLE_HS_IO_DISPLAY_YESNO   - Numeric comparison pairing
      BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
  */
  //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
  //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
  /** 2 different ways to set security - both calls achieve the same result.
      no bonding, no man in the middle protection, secure connections.

      These are the default values, only shown here for demonstration.
  */
  //NimBLEDevice::setSecurityAuth(false, false, true);
  NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);

  /** Optional: set the transmit power, default is 3db */
  NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */

  /** Optional: set any devices you don't want to get advertisments from */
  // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
  /** create new scan */
  NimBLEScan *pScan = NimBLEDevice::getScan();

  /** create a callback that gets called when advertisers are found */
  pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());

  /** Set scan interval (how often) and window (how long) in milliseconds */
  pScan->setInterval(97);
  pScan->setWindow(37);
  //pScan->setMaxResults(0); // do not store the scan results, use callback only.

  /** Active scan will gather scan response data from advertisers
      but will use more energy from both devices
  */
  pScan->setActiveScan(false);
  /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
      Optional callback for when scanning stops.
  */
  while (1) {
    if (!clientConnected) {
      if (!pScan->isScanning()) {
        pScan->start(30, scanEndedCB, false);
      } else {
        vTaskSuspend(scanTask);
      }
    } else {
      scanTaskRunning = false;
      vTaskSuspend(scanTask);
    }
    //pScan->stop();
  }

}

bool BLESetup() {

  NimBLEDevice::init("");

  xTaskCreatePinnedToCore(
    scanTaskFunction,         // Funzione del task
    "ScanTask",               // Nome del task
    10000,                     // Dimensione dello stack del task
    NULL,                      // Parametri del task
    1,                         // Priorità del task
    &scanTask,                 // Handle del task
    0                          // Core del processore su cui eseguire il task (0 o 1)
  );

  return true;
}

bool checkBLEServices() {
  if (doConnect) {
    if (connectToServer()) {
      Serial.println("Success! we should now be getting notifications, scanning for more!");
      doConnect = false;
      clientConnected = true;
      vTaskSuspend(scanTask);
      scanTaskRunning = false;
      return true;
    } else {
      Serial.println("Failed to connect, starting scan");
      doConnect = true;
      if (!scanTaskRunning) {
        vTaskResume(scanTask);
        scanTaskRunning = true;
      }
    }
  } else if (!clientConnected && !scanTaskRunning) {
    Serial.println("Not connected to bluetooth remote, scanning...");
      vTaskResume(scanTask);
      scanTaskRunning = true;
  }
  return false;
}

bool isBluetoothConnected() {
  return clientConnected;
}

setup():
Serial.print("Starting BLE Services"); if (!BLESetup()) { Serial.print("Can't start BLE Services!"); while (1) delay(10); }

p.s. this is the whole library I use to manage a bluetooth remote control, many things maybe you don't care about, look directly for pScan (object I use to call the scan start) if you don't want to look at the whole code!

@h2zero
Copy link
Owner

h2zero commented Dec 29, 2023

A quick look suggests that the scan task is being suspended in various situations but never resumed so the scan is never restarted.

@h2zero
Copy link
Owner

h2zero commented Jun 5, 2024

Closing this as it does not appear to be an issue with the library.

@h2zero h2zero closed this as completed Jun 5, 2024
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

2 participants