BLE Pairing not possible with Nuki 2.0

You are already on a good track. I think you are just missing a few things here:

  • For the pairing you need to use the Service UUID a92ee100-5501-11e4-916c-0800200c9a66 and the characteristics UUID a92ee101-5501-11e4-916c-0800200c9a66. The UUIDs you used are for after the pairing, to open the lock for example (the keyturner service)
  • You need to registerForNotify() before writing into the characteristic
  • You need to call registerForNotify(callback, FALSE) to get indications (with the most recent ESP32/BLE libraries)
  • Writing “0x0100030027A7” is not correct. You don’t want to send that string, but the actual byte values instead. This will write something entirely different (the string will translate to 0x30, 0x78, 0x30, 0x31…). Try writing a uint8_t array instead

Can’t easily share my code I’m afraid, as that is company owned. I can see if can extract some examples if you hit a will

Thanks a lot Felix for your advices. I checked every suggestions you’ve made. No success.
Point 1 : service & characteristic : done,
Point 2 : done, with
const uint8_t indicationOn[] = {0x2,0x0}; pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)indicationOn,2,true);
pRemoteCharacteristic->registerForNotify(notifyCallback, false);
Point 3 : done with the latest ESP32/BLE library
Point 4 : tried with something like : uint8_t arrayFV[] = {0x01,0x00,0x03,0x00,0x27,0xA7}
-> and pRemoteCharacteristic->writeValue(arrayFV, 6);
Did I miss something ?

This not necessary, as registerForNotify() already does that.

Other than that I don’t think you are missing anything obvious as far as I can tell. Can you share your updated code again? I can take a look again this evening and on the weekend.

1 Like

Thank you very much Felix.
Here is my entire code.

/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
#include "esp_log.h"
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("a92ee100-5501-11e4-916c-0800200c9a66");//Pairing service

// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("a92ee101-5501-11e4-916c-0800200c9a66");//Characteristic of pairing service
//std::vector<uint8_t> response(200);
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
uint8_t arrayFV[] = {0x01,0x00,0x03,0x00,0x27,0xA7}; //"0x0100030027A7";//first value to send as an array of byte to initiate Nuki Pairing

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("******************** Notify callback for characteristic ");
    Serial.println(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);
 //   for(int i= 0;i<length;i++) response.push_back(*pData++);
} // end notifyCallback

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

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

bool connectToServer() {
    Serial.print("Forming a connection to ");
    std::string myNukiAddr="54:d2:72:39:19:71";
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    Serial.print(" Essai de connexion à Nuki");
  
    pClient->connect(myNukiAddr);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");
    delay(100);
    // 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");
    Serial.print("CanRead : ");
    Serial.println(pRemoteCharacteristic->canRead()); 
    Serial.print("CanWrite : ");
    Serial.println(pRemoteCharacteristic->canWrite()); 
    Serial.print("CanNotify : ");
    Serial.println(pRemoteCharacteristic->canNotify()); 
    Serial.print("CanIndicate : ");
    Serial.println(pRemoteCharacteristic->canIndicate()); 
   
    connected = true;
    Serial.print("connected : ");
    Serial.println(connected); 
}

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

    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.");
    } 
   //register for indication   
    const uint8_t indicationOn[] = {0x2,0x0};
    pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)indicationOn,2,true);
    pRemoteCharacteristic->registerForNotify(notifyCallback, false); //false = indication, true = notification
    delay(100);
   //write Value
    if(pRemoteCharacteristic->canWrite()) {
      pRemoteCharacteristic->writeValue(arrayFV, 1, true);
            delay(50);           
      }
     std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
} // End of setup


// This is the Arduino main loop function.
void loop() {
  while (connected) {
  Serial.println("debut loop...");
    Serial.println("Still connected, loop");
       // Read the value of the characteristic.
     std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());

      delay(1000); // Delay between loops.
    } 
    Serial.println("not connected");
    
} // End of loop

Hi! Hope it is o.k. I corrected the markup for your post so the code is in one piece. :slight_smile:

Btw., very nice to see you helping out each other where we can not provide much help ourselfs! :+1:

1 Like

You made one mistake, other than that it worked right away. I made some notes marked with //fw:

/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
#include "esp_log.h"
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("a92ee100-5501-11e4-916c-0800200c9a66");//Pairing service

// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("a92ee101-5501-11e4-916c-0800200c9a66");//Characteristic of pairing service
//std::vector<uint8_t> response(200);
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
uint8_t arrayFV[] = {0x01,0x00,0x03,0x00,0x27,0xA7}; //"0x0100030027A7";//first value to send as an array of byte to initiate Nuki Pairing

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("******************** Notify callback for characteristic ");
    Serial.println(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    //fw: don't print this directly. the value is binary, not a 0-terminated string
    //Serial.println((char*)pData);
    debug_dump_binary(pData, length);
 //   for(int i= 0;i<length;i++) response.push_back(*pData++);
} // end notifyCallback

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

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

bool connectToServer() {
    Serial.print("Forming a connection to ");
    std::string myNukiAddr="54:d2:72:39:19:71";
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    Serial.print(" Essai de connexion à Nuki");
  
    pClient->connect(myNukiAddr);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");
    delay(100);
    // 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");
    Serial.print("CanRead : ");
    Serial.println(pRemoteCharacteristic->canRead()); 
    Serial.print("CanWrite : ");
    Serial.println(pRemoteCharacteristic->canWrite()); 
    Serial.print("CanNotify : ");
    Serial.println(pRemoteCharacteristic->canNotify()); 
    Serial.print("CanIndicate : ");
    Serial.println(pRemoteCharacteristic->canIndicate()); 
   
    connected = true;
    Serial.print("connected : ");
    Serial.println(connected); 
}

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

    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.");
    } 
   //register for indication   
   //fw: don't need this. this is done by registerForNotify()
    //const uint8_t indicationOn[] = {0x2,0x0};
    //pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)indicationOn,2,true);
    pRemoteCharacteristic->registerForNotify(notifyCallback, false); //false = indication, true = notification
    delay(100);
   //write Value
    if(pRemoteCharacteristic->canWrite()) {
      //fw: and here's your problem. you were sending the first byte only (writeValue(arrayFV, 1)
      pRemoteCharacteristic->writeValue(arrayFV, sizeof(arrayFV), true);
            delay(50);           
      }
     std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
} // End of setup


// This is the Arduino main loop function.
void loop() {
  //fw: I don't think you should loop here. loop() is looping anyways
  Serial.println("debut loop...");
  if (connected) {
    Serial.println("Still connected, loop");
       // Read the value of the characteristic.
     std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());

      delay(1000); // Delay between loops.
    }
    else { 
     Serial.println("not connected");
    }
    
} // End of loop

void debug_dump_binary(const void* buffer, size_t size)
{

  for(size_t i = 0; i < size; ++i)
  {
    if(i > 0 && (i % 8) == 0)
    {
      Serial.println("");
    }
    Serial.printf("%0x ", ((const uint8_t*) buffer)[i]);
  }
}

Now you will need to do some crypto handshaking and encryption. I suggest you fight through that part on your own, provides a nice practice.

One more tip for dealing with the BLE library: it throws exceptions when the lock disconnects. That is why your code crashes after a few seconds. You just need to have some proper exception handling with try-catch blocks. The tricky thing here however: it sometimes throws Exceptions and sometimes it throws Exceptions* . Best practice here is probably to always have two catch blocks; one for Exception objects and one for Exception pointers. Like this:

catch(const std::exception* e)
{
  //handle it
  delete e;
}
catch(const std::exception& e)
{
  //handle it
}

Thanks again Felix.
Now I get answer from Nuki. The key point was the way to read pData value (using your “debug_dump_binary())” is ok.
I can now go further to the next steps, thks to you.
I’ll keep you in touch.

It worked fine for me on my LolinD32…do you have the code for next steps? Check lock status and open/close?

Hi,

I have started writing an arduino library for use with an ESP32 to connect/configure a Nuki smart lock 2.0 via BLE.
I intend to make this open source on GIT once it is working, for others to use and improve…

I have started with the BLE API (which looks really well documented btw) but I allready get stuck at the first part connecting.

I wrote my own code but as it is not working I was looking for a fix and came across the code in this post:

It seems I get a timeout on the first step; connect BLE to Nuki (pClient->connect(myNukiAddr);).
This happens after 30 seconds, which I suspect is initiated by the Nuki as I see the same happening in NRFconnect.

It looks a bit like the ESP32 tries to establish a connection but the Nuki does not respond…? And the ESP BLE connect needs to finish before you can start registering on chars and sending the messages…

Any help is appreciated.

This is the code I used (as allready present in this topic, only changed the BLE address)

/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
#include "esp_log.h"
#include "Arduino.h"
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("a92ee100-5501-11e4-916c-0800200c9a66");//Pairing service

// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("a92ee101-5501-11e4-916c-0800200c9a66");//Characteristic of pairing service
//std::vector<uint8_t> response(200);
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
uint8_t arrayFV[] = {0x01,0x00,0x03,0x00,0x27,0xA7}; //"0x0100030027A7";//first value to send as an array of byte to initiate Nuki Pairing

void debug_dump_binary(const void* buffer, size_t size)
{

  for(size_t i = 0; i < size; ++i)
  {
if(i > 0 && (i % 8) == 0)
{
  Serial.println("");
}
Serial.printf("%0x ", ((const uint8_t*) buffer)[i]);
  }
}

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
Serial.print("******************** Notify callback for characteristic ");
Serial.println(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
//fw: don't print this directly. the value is binary, not a 0-terminated string
//Serial.println((char*)pData);
debug_dump_binary(pData, length);
 //   for(int i= 0;i<length;i++) response.push_back(*pData++);
} // end notifyCallback

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

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

bool connectToServer() {
Serial.print("Forming a connection to ");
std::string myNukiAddr="54:d2:72:4F:98:48";

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

pClient->setClientCallbacks(new MyClientCallback());

// Connect to the remove BLE Server.
Serial.print(" Essai de connexion à Nuki");
  
pClient->connect(myNukiAddr);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
delay(100);
// 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");
Serial.print("CanRead : ");
Serial.println(pRemoteCharacteristic->canRead()); 
Serial.print("CanWrite : ");
Serial.println(pRemoteCharacteristic->canWrite()); 
Serial.print("CanNotify : ");
Serial.println(pRemoteCharacteristic->canNotify()); 
Serial.print("CanIndicate : ");
Serial.println(pRemoteCharacteristic->canIndicate()); 
   
connected = true;
Serial.print("connected : ");
Serial.println(connected);
return connected;
}

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

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.");
} 
   //register for indication   
   //fw: don't need this. this is done by registerForNotify()
//const uint8_t indicationOn[] = {0x2,0x0};
//pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)indicationOn,2,true);
pRemoteCharacteristic->registerForNotify(notifyCallback, false); //false = indication, true = notification
delay(100);
   //write Value
if(pRemoteCharacteristic->canWrite()) {
  //fw: and here's your problem. you were sending the first byte only (writeValue(arrayFV, 1)
  pRemoteCharacteristic->writeValue(arrayFV, sizeof(arrayFV), true);
        delay(50);           
  }
 std::string value = pRemoteCharacteristic->readValue();
  Serial.print("The characteristic value was: ");
  Serial.println(value.c_str());
} // End of setup


// This is the Arduino main loop function.
void loop() {
  //fw: I don't think you should loop here. loop() is looping anyways
  Serial.println("debut loop...");
  if (connected) {
Serial.println("Still connected, loop");
   // Read the value of the characteristic.
 std::string value = pRemoteCharacteristic->readValue();
  Serial.print("The characteristic value was: ");
  Serial.println(value.c_str());

  delay(1000); // Delay between loops.
}
else { 
 Serial.println("not connected");
}

} // End of loop

This is the output where you see I get a onDisconnect:

Starting Arduino BLE Client application...
Forming a connection to  - Created client
 Essai de connexion à Nuki[D][BLEClient.cpp:96] connect(): >> connect(54:d2:72:4f:98:48)
[D][BLEDevice.cpp:593] addPeerDevice(): add conn_id: 0, GATT role: client
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:158] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEClient.cpp:158] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
onDisconnect
 - Connected to server
[I][BLEDevice.cpp:604] removePeerDevice(): remove: 0, GATT role client
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown
[D][BLEDevice.cpp:148] gattClientEventHandler(): gattClientEventHandler [esp_gatt_if: 4] ... Unknown

omg, I found it. bit ashamed to say but I made a typo in the BLE address :disappointed:
note to self: look for the most obvious solution/cause when having an issue

1 Like

I wrote a Nuki Fob based on the NRF51822. I my code I do not register for indications and it’s still able to pair and perform lock/unlock actions on a V1 Nuki. Apparently, I still receive indications despite not writing to the CCCD. Now users report they can’t pair with their V2 Nuki. Can you confirm that enabling indications is necessary for the V2 Nuki, but not the V1?

@i-connect any news about your lib and the project to release it on Git?
I would be really interested.

Sorry have some other prio’s to work on at the moment. But I do intend to continue with it , will probably be working on it 2nd half of this year.

1 Like

EDIT: Never mind, I think I found it on: https://github.com/I-Connect/NukiBleEsp32
Thanks a lot!

Hi Jeroen,

I‘d like to build an receiver for the Nuki Fob so I can trigger the Opener via HTTP as it is not reachable with the FOB from the outside. If you could provide any code that already has the basic bluetooth and Nuki stuff it would be super helpful and save me a lot of time. Just getting the latest non-working code would already help. :slight_smile:

Thanks,
Panda

thanky you for your work. After some problems with uploading the .bin file, i got it.
But my ibeacon has Pin25 and Pin26 for led and button instead of Pin11 and Pin30. It works with my nuki. The only thing i got stuck now is to use your source code, change the Pin there and create a .bin. Have some Problems with SDK and the Makefile.

@jeroen and the others thanky you for your work.
I started some month ago with Key-Pad/RFID/Fingerprint and connection to Nuki. At the moment i use Arduino Nano with Wiegand Protocol, some Relais and a Nuki Fob to open Nuki KT. As next step i want to combine Wiegand Protocol and Nuki open-close function in one ESP32. Did someone try the same or has some hints ?

Thank you,
uvs1313de

haven’t worked with wiegand yet but if you have it working on a Nano it should be no issue running it on an esp32 and combining it with the NukiBleEsp32 lib

thank you for your reply. I will have a look at this. I build my sketch in Arduino IDE, so i will add NukiBleEsp32 to this (maybe have to convert a bit)

Hi all, does Nuki 3.0 also support the BLE pairing with an esp32?

yes it does, see:
https://github.com/I-Connect/NukiBleEsp32