NodeJS BLE Applications using BLENO on Arch Linux

BLENO is a greate NodeJS based library for building applications that communicat with other devices (Smartphones, tables, sensor tags) using Bluetooth Low Energy (BLE).

This post is just to quickly document some requirements for successfully use the BLENO library, in my case, on Arch Linux running the latest Plasma (KDE) desktop.

The tools:

Most the information available on the internet for using and controlling the bluetooth adapter uses the now deprecated tools hcitool, hciconfig and so on. Check here the deprecated list of commands.

So we need to use the new tools from the latest Bluez (Bluetooth Linux implementation): btmgm, btinfo, …

Making Bleno examples work:

The simplest example to try out the BLENO library is the battery example located at: […]/bleno/examples/battery-service

First let’s check if our computer/laptop bluetooth adapter is available: Note that all commands must be ran as the root user:

root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
Failed to open HCI user channel
root@pcortex:/opt/bleno/examples/battery-service# 

This issue can be circumvented by stopping the higher level bluetooth stack:

root@pcortex:/opt/bleno/examples/battery-service# systemctl stop bluetooth
root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
HCI version: 6
HCI revision: 7869
LMP version: 6
LMP subversion: 64512
Manufacturer: 2
root@pcortex:/opt/bleno/examples/battery-service#

In case of previously disabling the Bluetooth through the graphical interface:

Disabling the Bluetooth here will have this behaviour (in this case the bluetooth service is still running):

root@pcortex:/opt/bleno/examples/battery-service# systemctl start bluetooth   (<- After this disable bluetooth on the graphical interface)
root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
Failed to open HCI user channel
root@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
Set Powered for hci0 failed with status 0x12 (Blocked through rfkill)
root@pcortex:/opt/bleno/examples/battery-service# 

Even stopping the Bluetooth service keeps the BT adapter disabled:

root@pcortex:/opt/bleno/examples/battery-service# systemctl stop bluetooth
root@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
Set Powered for hci0 failed with status 0x12 (Blocked through rfkill)
root@pcortex:/opt/bleno/examples/battery-service#

We can check this with the rfkill command:

root@pcortex:/opt/bleno/examples/battery-service# rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
2: hci0: Bluetooth
        Soft blocked: yes
        Hard blocked: no
root@pcortex:/opt/bleno/examples/battery-service# 

We can unblock now the adapter:

root@pcortex:/opt/bleno/examples/battery-service# rfkill unblock 2
root@pcortex:/opt/bleno/examples/battery-service# rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
2: hci0: Bluetooth
        Soft blocked: no
        Hard blocked: no
root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
HCI version: 6
HCI revision: 7869
LMP version: 6
LMP subversion: 64512
Manufacturer: 2
root@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
hci0 class of device changed: 0x00010c
hci0 Set Powered complete, settings: powered bondable ssp br/edr le secure-conn 
root@pcortex:/opt/bleno/examples/battery-service# 

So why we are having all this work for making sure that the BT adapter is powered on AND the bluetooth stack is stopped (systemctl stop bluetooth).

The answer is quite simple. If we don’t do this the BLENO examples will seem to work (they start) but the BLE advertised services are the bluetooth Bluez services and not our code.

To explain, check the following behaviour where we start the BLENO Battery Service with the Bluetooth stack started:

root@halcyon:/opt/bleno/examples/battery-service# systemctl start bluetooth
root@halcyon:/opt/bleno/examples/battery-service# node main.js 
on -> stateChange: poweredOn
on -> advertisingStart: success
setServices: success

Using the Nordic nRF Connect Android App we can see the non working behaviour vs what we should expect from the Bleno Battery example:

BLE Scan Results

Pressing Connect we can see on Client that no service are provided. This is due to the fact that the desktop bluetooth is enabled):

Now let’s disable the bluetooth stack (which powers the BT adapter) and start again the Bleno Battery example:

root@pcortex:/opt/bleno/examples/battery-service# systemctl stop bluetooth
root@pcortex:/opt/bleno/examples/battery-service# node main.js 

Example hangs in here, because BT adapter is disabled/off

^Croot@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
hci0 class of device changed: 0x00010c
hci0 Set Powered complete, settings: powered bondable ssp br/edr le secure-conn 
root@pcortex:/opt/bleno/examples/battery-service# node main.js 
on -> stateChange: poweredOn
on -> advertisingStart: success
setServices: success

And now if we scan again and connect to the Battery example with our mobile phone through the Nordic application we have:

It works now!

We can confirm that because on the file battery-service.js the service identifier is defined:

function BatteryService() {
  BatteryService.super_.call(this, {
      //uuid: '180F',
      uuid: 'ff51b30e-d7e2-4d93-8842-a7c4a57dfb07',
      characteristics: [
          new BatteryLevelCharacteristic()
      ]
  });
}

and it is the same detected by the Android application.

Advertisements

ESP8266 – Logging data in a backend – AES and Crypto-JS

After building, on the previous posts, the Node-Red based backend to support E2EE (End to End Encryption) so we can log data into a central server/database, from our devices securely, without using HTTPS, we need now to build the firmware for the ESP8266 that allows it to call our E2EE backend.

The firmware for the ESP8266 must gather the data that it wants to send, get or generate the current sequence number for the data (to avoid replay attacks), encrypt the data and send it to the backend.
On the backend we are using the Java script library for cryptographic functions Crypto-js, and specifically we are encrypting data with the encryption algorithm AES. So all we need is to encrypt our data with AES on the ESP8266, send it to the Node-Red Crypto-js backend, decrypt it and store it, easy right?

Not quite, let’s see why:

Crypto-js and AES:
We can see that on my Node-Red function code and testing programs I’m using something similar to the following code example:

var CryptoJS = require("crypto-js");
var message  = "Message to encrypt";
var AESKey   = '2B7E151628AED2A6ABF7158809CF4F3C';

// Encrypt
var ciphertext = CryptoJS.AES.encrypt(message, AESKey );

console.log("Cypher text in Base64: " ,  ciphertext.toString(CryptoJS.enc.base64) );

// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext.toString(), AESKey );
var plaintext = bytes.toString(CryptoJS.enc.Utf8);

console.log("Decrypted message UTF8 decoded: ", plaintext);

Several points regarding the above code need clarification:

The code variable AESKey the way it is used on the above example encrypt and decrypt functions isn’t really a key but a passphrase from where the real key and an initialization vector or salt value is generated (I’m using the names interchangeably, but they are not exactly the same thing except they are public viewable data that must change over time/calls).
The use for the generated key is self explanatory, but the initialization vector (IV) or salt value is used to allow that the encrypted data output to be not the same for the same initial message. While the key is kept constant and secret to both parties, the IV/salt changes between calls, which means that the above code, when run multiple times, will never produce the same output for the same initial message.

Still referring to the above code, the algorithm that generates the key from the passphrase is the PBKDF2 algorithm. More info at Crypto-js documentation. At the end of the encryption the output is a OpenSSL salted format that means that the output begins by the signature id: Salted_, followed by an eight byte salt value, and after the salt, the encrypted data.

So if we want use the API has above on the node-js/crypto-js side, we need to implement on the ESP8266 side both the AES and PBKDF2 algorithms.

I decided not to do that, first because finding a C/C++ implementation of the PBKDF2 algorithm that could be portable and worked on the ESP822 proved difficult, and second the work for porting it to the ESP8266 won’t be needed if I use a KEY/IV directly, and so I decided to use the more standard way of providing an AES key and an initialization vector for encrypting and decrypting data.

In the case of Node-JS and Crypto-JS when using an explicit key and IV the code looks like:

var CryptoJS = require("crypto-js");
var request = require('request');

// The AES encryption/decription key to be used.
var AESKey = '2B7E151628AED2A6ABF7158809CF4F3C';

// The JSON object that we want to encrypt and transmit
var msgObjs = {"data":{"value":300}, "SEQN":145 };

// Convert the JSON object to string
var message = JSON.stringify(msgObjs);

var iv = CryptoJS.enc.Hex.parse('0000000000000000');
var key= CryptoJS.enc.Hex.parse(AESKey);

// Encrypt
var ciphertext = CryptoJS.AES.encrypt(message, key , { iv: iv } );

//console.log("Cypher: ", ciphertext );
console.log("Cypher text: " ,  ciphertext.toString(CryptoJS.enc.base64) );
console.log(" ");

console.log("=============================================================================");
console.log(" ");
console.log("Let's do a sanity check: Let's decrypt: ");

// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext.toString(), key , { iv: iv} );
var plaintext = bytes.toString(CryptoJS.enc.Utf8);

console.log("Decrypted message UTF8 decoded: ", plaintext);

Now, with above code, where the IV is always initialized to the same value, in this case ‘0000000000000000’, we can see when running the above code several times that the output is always the same since the IV is kept constant. Also the encrypted output is now just the raw encrypted data and not the Openssl format.

So to make the above code secure we must randomize the IV value for producing an output that is always different, even from several program runs when encrypting the same source data.

As a final note, if we count the number of HEX characters on the Key string, we find out that they are 16 bytes, which gives a total of 128 key bits. So the above example is using AES128 encryption, and with default Crypto-js block mode and padding algorithms which are CBC (Chain block mode) and pkcs7.

Interfacing Crypto-js and the ESP8266:
Since we are using AES for encrypting data and decrypting data, we need first to have an AES library for the ESP8266. The AES library that I’m using is this one Spaniakos AES library for Arduino and RPi. This library uses AES128, CBC and pkcs7 padding, so it ticks all boxes for compatibility with Crypto-js…

I just added the code from the above library to my Sming project and also added this Base64 library so that I can encode to and from Base64.

The only remaining issue was to securely generate a truly random initialization vector. And while at first I’ve used some available libraries to generate pseudo-random numbers to real random numbers, I’ve found out that the ESP8266 seems to have already a random number generator that is undocumented: Random number generator

So to generate a random IV value is as easy as:

uint8_t getrnd() {
    uint8_t really_random = *(volatile uint8_t *)0x3FF20E44;
    return really_random;
}

// Generate a random initialization vector
void gen_iv(byte  *iv) {
    for (int i = 0 ; i < N_BLOCK ; i++ ) {
        iv[i]= (byte) getrnd();
    }

So our ESP8266 code is as follow:

Global variables declarations:
The N_Block defines the encryption block size, that for AES128 is 16 bytes.

#include "AES.h"
#include "base64.h"

// The AES library object.
AES aes;

// Our AES key. Note that is the same that is used on the Node-Js side but as hex bytes.
byte key[] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C };

// The unitialized Initialization vector
byte my_iv[N_BLOCK] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

// Our message to encrypt. Static for this example.
String msg = "{\"data\":{\"value\":300}, \"SEQN\":700 , \"msg\":\"IT WORKS!!\" }";


The example function ESP8266 Sming code is:

void testAES128()  {

    char b64data[200];
    byte cipher[1000];
    byte iv [N_BLOCK] ;
    
    Serial.println("Let's encrypt:");
    
    aes.set_key( key , sizeof(key));  // Get the globally defined key
    gen_iv( my_iv );                  // Generate a random IV
    
    // Print the IV
    base64_encode( b64data, (char *)my_iv, N_BLOCK);
    Serial.println(" IV b64: " + String(b64data));
       
    Serial.println(" Mensagem: " + msg );
 
    int b64len = base64_encode(b64data, (char *)msg.c_str(),msg.length());
    Serial.println (" Message in B64: " + String(b64data) );
    Serial.println (" The lenght is:  " + String(b64len) );
    
    // For sanity check purpose
    //base64_decode( decoded , b64data , b64len );
    //Serial.println("Decoded: " + String(decoded));
    
    // Encrypt! With AES128, our key and IV, CBC and pkcs7 padding    
    aes.do_aes_encrypt((byte *)b64data, b64len , cipher, key, 128, my_iv);
    
    Serial.println("Encryption done!");
    
    Serial.println("Cipher size: " + String(aes.get_size()));
    
    base64_encode(b64data, (char *)cipher, aes.get_size() );
    Serial.println ("Encrypted data in base64: " + String(b64data) );
      
    Serial.println("Done...");
}

When the above code/function is executed on the ESP8266 it outputs the following:

Let's encrypt:
 IV b64: cAFviaDMHejlteGn9/4eQQ==
 Mensagem: {"data":{"value":300}, "SEQN":700 , "msg":"IT WORKS!" }
 Message in B64: eyJkYXRhIjp7InZhbHVlIjozMDB9LCAiU0VRTiI6NzAwICwgIm1zZyI6IklUIFdPUktTISIgfQ==
 The lenght is:  76
Encryption done!
Cipher size: 80
Encrypted data in base64: /1aZRwVaw3jv+ct8HS4pCV5lThvTG70M90ARiyAsIDYMkfJE3w8F3bgxaOKVA0rX4m1Mq50VVN0u9gRw9F2gKE4r2OcY8oECv8bKT80F9pY=
Done...

And now we can feed the above Base64 IV and encrypted data to our decoding program in Node-Js using Crypto-JS:

var CryptoJS = require("crypto-js");
var request = require('request');

var esp8266_msg = '/1aZRwVaw3jv+ct8HS4pCV5lThvTG70M90ARiyAsIDYMkfJE3w8F3bgxaOKVA0rX4m1Mq50VVN0u9gRw9F2gKE4r2OcY8oECv8bKT80F9pY=';
var esp8266_iv  = 'cAFviaDMHejlteGn9/4eQQ==';

// The AES encryption/decryption key to be used.
var AESKey = '2B7E151628AED2A6ABF7158809CF4F3C';

var plain_iv =  new Buffer( esp8266_iv , 'base64').toString('hex');
var iv = CryptoJS.enc.Hex.parse( plain_iv );
var key= CryptoJS.enc.Hex.parse( AESKey );

console.log("Let's decrypt: ");

// Decrypt
var bytes  = CryptoJS.AES.decrypt( esp8266_msg, key , { iv: iv} );
var plaintext = bytes.toString(CryptoJS.enc.Base64);
var decoded_b64msg =  new Buffer(plaintext , 'base64').toString('ascii');
var decoded_msg =     new Buffer( decoded_b64msg , 'base64').toString('ascii');

console.log("Decrypted message: ", decoded_msg);

and the output is:

Decrypted message:  {"data":{"value":300}, "SEQN":700 , "msg":"IT WORKS!" }

So, as the message shows, it WORKS!. AES encryption on the ESP8266 to a Node-JS Crypto-JS based code where decryption occurs.

Final notes:
So all is needed now is to build on the ESP8266 side the message with the encrypted data and the IV, and send through plain HTTP a JSON object to the Node-Red back end.

{
  "msg":"/1aZRwVaw3jv+ct8HS4pCV5lThvTG70M90ARiyAsIDYMkfJE3w8F3bgxaOKVA0rX4m1Mq50VVN0u9gRw9F2gKE4r2OcY8oECv8bKT80F9pY=",
  "iv":"cAFviaDMHejlteGn9/4eQQ=="
}

On the Node-Red back end the decryption can now be done easily as was shown on the above testing code.

IoT device provisioning and tracking

If we are deploying several IoT devices, above a certain number it becomes a hard task to know which ones are alive and working, their location, and since they may have different functions/purposes, to keep track of their configuration. This last problem is exacerbated if we need to keep and maintain different code bases and firmwares for each device.

Device provisioning and some cleaver firmware code, can help to bring these issues under control. Device provisioning is nothing more than a fancy name for a central server that holds information for each device, it’s configuration and status and even might allow some form of monitoring. In advanced usage it might let an operator to push firmware or configuration updates to certain devices, without the need to physical get them.

So this post is about my simple device provisioning that allows to keep track of my ESP8266 devices, running the Sming firmware, and control, in this initial stage, some of it’s configuration.

The solution is based in three components:

  • NodeJs based REST server that holds information in a SQLite database
  • An Angular.Js based web application to managed the device configuration
  • Sming based firmware for the ESP8266

The REST server
The REST server is based in javascript running on a NodeJs server, which uses Express and Body-Parser to create a simple and quick REST server. The REST protocol uses the HTTP operations GET, POST and PUT to define operations like get data, create data or update data. Due to the Express modules, we can build a simple interface to a database that translates REST operations to database operations. The Body-Parser module allows to communicate purely using JSON, and so takes care of all the details of coding and decoding JSON data communicated over HTTP.
Finally there is one important thing, at least for the browser front-end that is the CORS protection. CORS, Cross Origin Resource Sharing is a security feature that browsers use to not connect to anything else other than the originating domain of the page that are showing. To allow our REST server to be called by a running application on a browser (in our case, our Angular.Js frontend), the server must explicitly allow this. So we also need a CORS module for our REST server.
Finally I’ve chosen a SQLITE database due to it’s simpler configuration. No need to setup servers…
To allow our REST server to run we need to install locally to our application the folowing modules: npm install express node-sqlite3 body-parser cors

// Define and call the packages necessary for the building the REST API.
var sqlite3    = require('sqlite3').verbose();
var db         = new sqlite3.Database('data/restDB.sqlite');
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');
var cors       = require('cors');

After the module instantiation, and note that the database is created in the data subdirectory, we can create if needed our tables and fill them up with default data if needed:

db.serialize(function() {  // All database operations will be executed in series.
    // some code here    
    db.run("CREATE TABLE IF NOT EXISTS devices ( deviceid TEXT , name TEXT , lastseen TEXT , ssid TEXT, ipaddr TEXT, cfgsn INTEGER, flags INTEGER, data TEXT, datec TEXT )");
    // some more code here
});

The db.serialize makes the database functions to run in series, one after each other.
The db.run will execute a SQL command, and in the above case the creation of the device provisioning table.

The schema is the following:

  • deviceid -> the device id as sent by the device. It should be unique. I use the MAC address of the ESP8266 that should be unique.
  • name -> a human friendly name for the device. It can be anything.
  • lastseen -> The device should periodically call the REST server to let us know that’s is alive and connected. The lastseen field is the last time that information was received.
  • ssid -> Shows the WiFi SSID to which the device is connected.
  • ipaddr -> Shows the device IPv4 address in use. Might not be reachable if the device is behind a NAT router.
  • cfsn -> Config serial number. To let the device know if the configuration it holds is older than the configuration on the table row.
  • flags -> For future use, but the idea is to allow pagination, and command/control/data separation.
  • data -> Configuration data for the device. In my case a JSON array of properties and values user defined.
  • datec -> The date that the record was create, which means when the device was first seen.

Now knowing what information we are using, we can build the REST server:

//Configure Express
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());

var router = express.Router();

// Root URI.
router.get('/', function(req, res) {
        res.json( {message: 'REST API' });
});

///// OUR CODE here

// Our base url is /api
app.use('/api', router);
app.listen(3000);

console.log("========================================");
console.log("Server started at http://localhost:3000/");

Excluding the ///// OUR CODE here this is the standard skeleton for the REST server that runs at port 3000 and hears for REST request on the /api base url.

For testing I recommend the FireFox plugin HTTPRequester that allows to simulate all the REST calls.

The devices REST interface

For the device provisioning I’ve implemented the REST end point devices which mean that we should call our REST server with the following URL’s: http://rest_server:3000/api/devices and http://rest_server:3000/api/devices/deviceid where deviceid is the device generated id.

For the first URL, so far I only implement the GET verb, which returns all registered devices:

router.route('/devices')
        .get ( function(req, res) {
           console.log("Devices list");
           db.all("SELECT ROWID, deviceid, name, lastseen, ssid, ipaddr, cfgsn, flags, data, datec  FROM devices",
              function( err , row ) {
                if (err) {
                  res.json( { status:'NOK', message: 'ERROR selecting data'} );
                } else {            
                  console.log('API: GET devices -> (' + row.length + ') records -> ' + JSON.stringify(row));
                   res.json( row );
                }                  
        });
      })

The function db.all returns an empty array if no results, or an array with row set. The row set is returned directly as a JSON array object to the calling application/device.

Selection_237

For the last URL, the implemented verbs are GET, to get the latest configuration and update the lastseen field, POST to register the device and/or returning the configuration, and PUT to update device information (used by the front end):

router.route('/devices/:id')
    .get( function(req, res) {
        var currdate = new Date();
        db.run("UPDATE devices SET lastseen='" + currdate + "' WHERE deviceid='" + req.params.id + "'");

        db.get("SELECT cfgsn, data FROM devices WHERE deviceid='"+req.params.id+"'",
          function( err , row ) {
            if (err) {    
              res.json( {status: 'NOK', message: 'Device get information failed' } );
            } else {
              if ( (typeof row == 'undefined') ) { 
                res.json( {status: 'NOK', message: 'Device NOT Registered' } );
              } else {
                res.json( row );
              }
            }
        });
    })    
    .put( function(req, res) {
        db.run("UPDATE devices SET name='"+req.body.name+"', cfgsn="+req.body.cfgsn.toString()+", data='"+req.body.data+"' WHERE deviceid='"+req.params.id+"'");  
        res.json( {status: 'OK', message: 'Device Updated' } );
    })    
    .post( function(req, res) {
        console.log("DEVICES -> POST Device request received: " + req.params.id);
        console.log("Body: " + JSON.stringify(req.body) );
        console.log("IP address: " + req.body.ipaddr );
        console.log("SSID: " + req.body.ssid );

        db.get("SELECT ROWID , deviceid, name, lastseen, ssid, ipaddr, cfgsn, flags, data, datec FROM devices WHERE deviceid='"+req.params.id+"'",
          function( err , row ) {
            if (err) {
              res.json( {status: 'NOK', message: 'Device Registration failed' } );
            } else {
              if ( (typeof row == 'undefined') ) {    // Device is new... if row is undefined (no results)
                var deviceid  = req.params.id;
                var name      = req.params.id;
                var lastseen  = new Date();
                var ssid      = req.body.ssid;
                var ipaddr    = req.body.ipaddr;
                var cfgsn     = "1";
                var flags     = "0";
                var data      = "";
                var datec     = lastseen;
                db.run("INSERT INTO devices ('deviceid','name','lastseen','ssid','ipaddr','cfgsn','flags','data','datec') VALUES ('" + deviceid + "', '" + name + "', '" + lastseen + "', '" + ssid + "', '" + ipaddr + "', " + cfgsn + ", " + flags + ", '" + data + "', '" + datec + "')");
                res.json( {status: 'OK', message: 'Device Registed' } );
             } else {
                res.json( row ); // Device already registered
             }
          }
       });
    })

The above REST verbs, namely the POST and PUT need a request body, to pass data from the source to the database.

In case of the POST verb the, body should be the following JSON: { “ipaddr”:”x.x.x.x”, “ssid”:”wifissid” }. For example:

Selection_238

We save the above code on a file, let it be restserver.js for example, and start our server with node restserver.js.

On the front end then we can have now a list of devices that are registered:

Selection_239

And selecting the device we can change the human readable name, and add configuration properties:

Selection_240

The front end, based on Angular Js isn’t completly ready, since it’s main purpose is to graph data from Sparkfuns Phant server.

Sming ESP8266 Code

To make this complete, we only need to start coding the ESP8266. I’ve abandoned NodeMcu and Lua language, due to several factors. Sming is a great firmware and already has support for a lot of devices and also includes a JSON library.

The sample Sming code is as simple as this:

#include <user_config.h>
#include <SmingCore/SmingCore.h>

#ifndef WIFI_SSID
    #define WIFI_SSID "defineme" // Put you SSID and Password here
    #define WIFI_PWD "passwd"
#endif

HttpClient provServer;  // Provisioning server client
String deviceid;

void onProvSent(HttpClient &client, bool successful )
{
    char cbuf[2048];
    StaticJsonBuffer<2048> jsonBuffer;
        
    if (successful)
        Serial.println("Registration request sent sucessufully");
    else
        Serial.println("Registration request has failed");
        
    String response = client.getResponseString();
    Serial.println("Server response: '" + response + "'"); 
        response.toCharArray( cbuf , 2048 );      
        JsonObject& config = jsonBuffer.parseObject( cbuf );
        
        if ( !config.success() ) {
            Serial.println("Error decoding JSON.");
        } else {
            Serial.println("Received config: " + config.toJsonString() );
        
            const char* name = config["name"];
            const char* datec= config["datec"];
            
            Serial.println ("Device Name: " + String(name) );
            Serial.println ("Date: " + String(datec) );
            // Process the JSON Array with the configuration data...

        }
}

void registerDevice ()
{
        String devIP;
        StaticJsonBuffer<256> jsonBuffer;
                
        devIP = WifiStation.getIP().toString();
        
        Serial.println("Sending device ID " + deviceid + " to provisioning server..." );
        Serial.println("Device IP : " + devIP );      
        JsonObject& config = jsonBuffer.createObject();
 
        config.addCopy("ipaddr", devIP );
        config.add("ssid"  , WIFI_SSID );
        
        provServer.setRequestContentType("application/json");
        provServer.setPostBody(config.toJsonString());
        provServer.downloadString("http://192.168.1.17:3000/api/devices/" + deviceid , onProvSent );
}

// Will be called when WiFi station was connected to AP
void connectOk()
{
    Serial.println("I'm CONNECTED");
        
        // Let's get the Mac address, that should be unique...
        deviceid = WifiStation.getMAC();
        deviceid.toUpperCase();
        Serial.println("Device ID: " + deviceid );
        
        // Register Device on the provisioning Server
        registerDevice();

    // Start send data loop
    // procTimer.initializeMs(25 * 1000, sendData).start(); // every 25 seconds
}

// Will be called when WiFi station timeout was reached
void connectFail()
{
    Serial.println("I'm NOT CONNECTED. Need help :(");

    // Start soft access point
    WifiAccessPoint.enable(true);
    WifiAccessPoint.config("CONFIG ME PLEEEEASE...", "", AUTH_OPEN);

    // .. some you code for configuration ..
}

void init()
{
    Serial.begin(SERIAL_BAUD_RATE); // 115200 by default
    Serial.systemDebugOutput(false); // Disable debug output to serial

    WifiStation.config(WIFI_SSID, WIFI_PWD);
    WifiStation.enable(true);
    WifiAccessPoint.enable(false);

    // Run our method when station was connected to AP (or not connected)
    WifiStation.waitConnection(connectOk, 20, connectFail); // We recommend 20+ seconds for connection timeout at start
}

And that’s it. After startup, the device after connecting to WiFi, makes a request to the provisioning server. If the request is from a new device it registers, if not it receives it’s configuration data. From this point we can do anything, namely pooling periodically the provisioning server to check for new configuration (by comparing the cfgsn hold by the device with the received one).