nkolban / esp32-snippets

Sample ESP32 snippets and code fragments
https://leanpub.com/kolban-ESP32
Apache License 2.0
2.35k stars 710 forks source link

BLE_Client ESP32 Arduino BLE Server RN4871 #1078

Open adrienlaveau opened 3 years ago

adrienlaveau commented 3 years ago

Dear all,

I have been reading extensively all the documentation I could find regarding my issue and could not find a solution so I will expose my problem here. Setup : BLE server : ATtiny 1614 sending via UART to RN4871 chip BLE Client : ESP32 using the code below on Arduino IDE

The code is the BLE_client example from Arduino with some debbuging modification of mine.

So I can find my device and match the MAC address BUT it seems there is no Service. The three lines below returns all 0. By the way I do not understand the difference between the three kind of Service (Data, Data UUID, UUID). Serial.println(advertisedDevice.getServiceDataCount()); Serial.println(advertisedDevice.getServiceDataUUIDCount()); Serial.println(advertisedDevice.getServiceUUIDCount()); I can see the server and the characteristic on nRF connect ( I took the Device and Characteristic UUID from thje application). THe RN4871 chip documentation also confirms the UUID here page 65.

I do not know where to look.

Client code

`

include "BLEDevice.h"

  //#include "BLEScan.h"

  // The remote service we wish to connect to.
  static BLEUUID serviceUUID("49535343-FE7D-4AE5-8FA9-9FAFD205E455");//("0x2902");//; // 49535343-fe7d-4ae5-8fa9-9fafd205e455");
                            //49535343-FE7D-4AE5-8FA9-9FAFD205E455
  // The characteristic of the remote service we are interested in.
  static BLEUUID    charUUID("49535343-1e4d-4bd9-ba61-23c647249616");
                            //49535343-1E4D-4BD9-BA61-23C647249616

  String My_BLE_Address = "04:91:62:9c:09:07";
  static BLEAddress *Server_BLE_Address;
  String Scaned_BLE_Address;

  static boolean doConnect = false;
  static boolean connected = false;
  static boolean doScan = true;
  static BLERemoteCharacteristic* pRemoteCharacteristic;
  static BLEAdvertisedDevice* myDevice;

  static BLEUUID *Server_BLE_UUID;
  String Scaned_BLE_UUID;

  static void notifyCallback(
    BLERemoteCharacteristic* pBLERemoteCharacteristic,
    uint8_t* pData,
    size_t length,
    bool isNotify) {
      Serial.print("Notify callback for characteristic ");
      Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
      Serial.print(" of data length ");
      Serial.println(length);
      Serial.print("data: ");
      Serial.println((char*)pData);
  }

  class MyClientCallback : public BLEClientCallbacks {
    void onConnect(BLEClient* pclient) {
    }

    void onDisconnect(BLEClient* pclient) {
      connected = false;
      Serial.println("onDisconnect");
    }
  };

  bool connectToServer() {
      Serial.println(sizeof(myDevice->getAddress().toString().c_str()));
      Serial.print("Forming a connection to ");
      Serial.println(myDevice->getAddress().toString().c_str());

      BLEClient*  pClient  = BLEDevice::createClient();
      Serial.println(" - Created client");

      pClient->setClientCallbacks(new MyClientCallback());
      Serial.print("BLABLA");
      // Connect to the remove BLE Server.
      pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
      Serial.println(" - Connected to server");

      // Obtain a reference to the service we are after in the remote BLE server.
      BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
      if (pRemoteService == nullptr) {
        Serial.print("Failed to find our service UUID: ");
        Serial.println(serviceUUID.toString().c_str());
        pClient->disconnect();
        return false;
      }
      Serial.println(" - Found our service");

      // Obtain a reference to the characteristic in the service of the remote BLE server.
      pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
      if (pRemoteCharacteristic == nullptr) {
        Serial.print("Failed to find our characteristic UUID: ");
        Serial.println(charUUID.toString().c_str());
        pClient->disconnect();
        return false;
      }
      Serial.println(" - Found our characteristic");

      // Read the value of the characteristic.
      if(pRemoteCharacteristic->canRead()) {
        std::string value = pRemoteCharacteristic->readValue();
        Serial.print("The characteristic value was: ");
        Serial.println(value.c_str());
      }

      if(pRemoteCharacteristic->canNotify())
        pRemoteCharacteristic->registerForNotify(notifyCallback);

      connected = true;
      return true;
  }
  /**
   * Scan for BLE servers and find the first one that advertises the service we are looking for.
   */
  class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
   /**
     * Called for each advertising BLE server.
     */
    void onResult(BLEAdvertisedDevice advertisedDevice) {
  //            int imax = advertisedDevice.getServiceUUIDCount();
      Serial.print("BLE Advertised Device found: ");
      Serial.println(advertisedDevice.toString().c_str());
         Server_BLE_Address = new BLEAddress(advertisedDevice.getAddress());
         Scaned_BLE_Address = Server_BLE_Address->toString().c_str();
         Serial.println(Scaned_BLE_Address);
      if(Scaned_BLE_Address == My_BLE_Address){
          Serial.println("MATCH !");
          BLEUUID service = advertisedDevice.getServiceUUID();
          if (service.equals(BLEUUID((uint16_t) 0x1801))) {
            Serial.println("We found a usefule device");
          }
          Serial.println(advertisedDevice.getServiceDataCount());
          Serial.println(advertisedDevice.getServiceDataUUIDCount());
          Serial.println(advertisedDevice.getServiceUUIDCount());

  //      for (uint32_t i = 0; i <= advertisedDevice.getServiceUUIDCount() -1; i++) 
  //      {
  //        Serial.printf("i : %i over %i \n",i,imax);
  //        int j = i;
  //        Serial.println(j);
  //        
  //        //Serial.printf("Have service UUID i% \n",advertisedDevice.haveServiceUUID());
  //        Serial.println(advertisedDevice.haveServiceUUID());
  //        Serial.println(advertisedDevice.haveServiceData());
  //        Serial.println("boom");
  //        Server_BLE_UUID = new BLEUUID(advertisedDevice.getServiceUUID(0));
  //        Serial.println("ok");
  //        Scaned_BLE_UUID = Server_BLE_UUID->toString().c_str();
  //        Serial.println(Scaned_BLE_UUID);
  //      }
  //      Serial.println("END LOOP");
      }
      // We have found a device, let us now see if it contains the service we are looking for.
      if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
        Serial.println("MATCHING SERVICE !!!!!");
        BLEDevice::getScan()->stop();
        myDevice = new BLEAdvertisedDevice(advertisedDevice);
        doConnect = true;
        doScan = true;

      } // Found our server
    } // onResult
  }; // MyAdvertisedDeviceCallbacks

  void setup() {
    Serial.begin(115200);
    Serial.println("Starting Arduino BLE Client application...");
    BLEDevice::init("");

    // Retrieve a Scanner and set the callback we want to use to be informed when we
    // have detected a new device.  Specify that we want active scanning and start the
    // scan to run for 5 seconds.
    BLEScan* pBLEScan = BLEDevice::getScan();
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
    //pBLEScan->setInterval(1349);
    //pBLEScan->setWindow(449);
    pBLEScan->setActiveScan(true);
    pBLEScan->start(2, false);
  } // End of setup.

  // This is the Arduino main loop function.
  void loop() {
    Serial.println("Start Loop");
    // If the flag "doConnect" is true then we have scanned for and found the desired
    // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
    // connected we set the connected flag to be true.
    if (doConnect == true) {
       Serial.println("Doconnect");
      if (connectToServer()) {
        Serial.println("We are now connected to the BLE Server.");
      } else {
        Serial.println("We have failed to connect to the server; there is nothin more we will do.");
      }
      doConnect = false;
    }

    // If we are connected to a peer BLE Server, update the characteristic each time we are reached
    // with the current time since boot.
    if (connected) {
      String newValue = "Time since boot: " + String(millis()/1000);
      Serial.println("Setting new characteristic value to \"" + newValue + "\"");

      // Set the characteristic's value to be the array of bytes that is actually a string.
     // //////////////////////// pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
    }else if(doScan){
      BLEDevice::getScan()->start(0);  // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
    }

    delay(1000); // Delay a second between loops.
  } // End of loop

`

Any help would be deeply appreciated.

chegewara commented 3 years ago

Advertised services and services in app are 2 different things. You can see if nRF connect shows any UUIDs when you scan. If this code returns all 0s, then most likely ATTiny is not advertising any UUIDs:

Serial.println(advertisedDevice.getServiceDataCount()); 
Serial.println(advertisedDevice.getServiceDataUUIDCount()); 
Serial.println(advertisedDevice.getServiceUUIDCount());
CLDiego commented 3 years ago

I have some experience with the RN4871 and I can say that it can give you a lot of headaches specially using the BLE transparent UART service.

Have you configured your module correctly? By the default the service for the transparent UART is not active, so you need to connect to the RN4871 and enter the command mode to activate the service.

I use the same UUIDs that you posted to talk to the RN4871 and turn on the notifications for the characteristic 49535343-1E4D-4BD9-BA61-23C647249616 .

adrienlaveau commented 3 years ago

Thanks for the quick feedback.

So indeed I read one post in which you were involved where someone was explaining that he got confused between both advertised services and services. BUt I could not find the post today. Concerning the ATtiny I can confirm that it "emits" info. Because I can see it on nRF Connect. I used the verb "emit" bause indeed I am, not sure that it is advertised. In fact I used the RN4871 chip as transparent UART. And from what I understand from the datasheet is that : What ever is sent on the serial to the RN4871 chip is then re-emitted by the RN4871 chip.

In the datasheet one can read "The Transparent UART TX Characteristic is used for data transmission by the Server or the Client. Once the Client Characteristic Configuration Descriptor (CCCD) of Transparent UART TX Characteristic is enabled, the Server sends data to the Client using the Notify property."

@CLDiego I used the following commands to activate transparant UART ` $$$

+

SS:C0 --> activates transparent UART

R,1 --> Reboot
` And it works as I can see it on the nRF connect.

Now when you say "turn on the notification for the characteristic" this is ,aybe where I a, doing so,ething wrong. Is it on client or server side ? How should it be done ?

Thanks a lot

CLDiego commented 3 years ago

There should be an example here or in the arduino IDE for handling notifications using this library. Essentially, whenever new data is generated and sent through the UART service it will send a notification to the client (I'm assuming you are using the RN4871 as a server) and the client will catch the notification along the new data. In lamest terms you should do something like this:

 pTXCharacteristic = pUARTService->getCharacteristic( charUUID);

pTXCharacteristic->registerForNotify(notifyCallback, false);

notifyCallback is your function that will handle the processing of the incoming data

adrienlaveau commented 3 years ago

@chegewara, in nRF connect the characteristic has two UUID A "big one" : 49535343-1E4D-4BD9-BA61-23C647249616 A "small one" : 0x2902 that seems to be the UUID of the descriptor Here I admit I do not understand the difference.

By the way I read the whle PDF document by Kolban (https://github.com/nkolban/esp32-snippets/blob/master/Documentation/BLE%20C%2B%2B%20Guide.pdf) which helped a lot to understand the library. Really weel explained and nice to read.

adrienlaveau commented 3 years ago

@CLDiego thanks.

Indeed I imagined this possibilty but when looking for example is the IDE it was only to set up the notification on server side. Not to receive on client side. I tried to find how to code it by myself but got a bit demotivated :)

Let me read properly your comment to find how I should integrate it in my code.

Thank you

CLDiego commented 3 years ago

The UUID descriptor contains the information about the characteristic. i.e. if you can read, write, notify etc. You can see it as a set of flags, the data is going to be published through the characteristic.

The RN4871 handles the notifications by itself, you only need to program it on the esp32. You should see your RN4871 as a UART bridge.

I would encourage you to look at the example for notifications here

adrienlaveau commented 3 years ago

Thanks. So this example is exactly the one I based my code on.

But it does not work as such because : This line always returns 0 as I do not have advertised service (only notification) if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

If I tweak the code to go for the server connect when hit finds a matching MAC adress then it crashes and reboot the ESP32 on the follfollowing line Serial.print("Forming a connection to "); Serial.println(myDevice->getAddress().toString().c_str());

adrienlaveau commented 3 years ago

Correction ... it worked.

I just removed the condition to check the service and to connect directly if the mac address matches. I do not understand why it was not working before.

I will check the steps I have been thourgh and explain what happend.

Thanks you very much.

adrienlaveau commented 3 years ago

So, I did the following to make it work. I added a check on the MAC Address and kind of ignore the check on advertised service as the RN4871 does not have one. This way I could make it work because the connection starts and as soon as the RN4871 sends data I get the notification and receive it.

Thank you very much for the help !

I documented all this here for the next one : http://127.0.0.1:8000/assignments/week13/#how-to-setup-the-rn4871

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
     * Called for each advertising BLE server.
     */
    void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
         Server_BLE_Address = new BLEAddress(advertisedDevice.getAddress());
         Scaned_BLE_Address = Server_BLE_Address->toString().c_str();
         Serial.println(Scaned_BLE_Address);

if(Scaned_BLE_Address == My_BLE_Address){    
doConnect = true;
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
}

// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
        Serial.println("MATCHING SERVICE !!!!!");
        BLEDevice::getScan()->stop();
        myDevice = new BLEAdvertisedDevice(advertisedDevice);
        doConnect = true;
        doScan = true;

} // Found our server
    } // onResult
}; // MyAdvertisedDeviceCallbacks