Bluetooth Low Energy Server on ESP32 development board

Step by step guide for the code installed on an ESP32 set up as Bluetooth Low Energy (BLE) server. We are going to prepare the sketch file in Arduino IDE and the ServerBLE class.

June 05, 2020

Objectives

  • BLE Server running on an ESP32 development board

What is covered

Code repository

Primers

Using the breadboard to wire up ESP32

To power up the board you may proceed however you want: a USB cable connected to your computer, a 5V DC 1A power adapter connected to the wall socket, or a trusty breadboard power supply like YwRobot 5 VDC & 3.3 VDC. Such a power supply will be handy if you are using sensors that require 5 VDC because ESP32 is running on 3.3 VDC. In such a case make sure you wire everything’s GND to the GND of the 5V power supply.

Please refer to the Fritzing file from above for details. You can ignore the parts related to the Arduino Nano, the LCD 2004, to the stepper motor. I will cover those in subsequent blog posts. Also, please read from above on how to install and set up the Arduino IDE and the ESP32 board.

I have found useful while uploading the sketch file into the ESP32 board to press the “BOOT” switch. It might become a headache to expect the board to always figure out that you are trying to upload something.

The Arduino sketch .ino file for the BLE server

To generate some test data for the BLE server I am using 3 temporary sensors: a photoresistor, a hall effect sensor, and a temperature and humidity sensor. We are going to import the DHT.h (DHT Sensor Library from Adafruit, required by the temperature and humidity DHT 11 sensor), Wire.h (required for the I2C communication with Arduino Nano, covered in a future blog post), Lcd.h (header of the custom class for the LCD 2004), ServerBLE.h (header of the custom class for the server).

We instantiate the lcd and server objects on lines 6-7 and we also assign values to the required constants for the I2C address, pins, and for the variables on lines 9-29. settings array is for storing the values received by the BLE server from a BLE client. previousMillis is used to debounce the data received from the sensors at a sensorsInterval of 100 miliseconds.

#include <DHT.h>
#include <Wire.h>
#include "Lcd.h"
#include "ServerBLE.h"

Lcd lcd;
ServerBLE server;

const int I2CSlaveAddress = 4;

const int translatorOEPin = 23;
const int translatorDelay = 10000;

const int size = 4;
int settings[size] = {0, 0, 0, 0};
int prevSettings[size] = {0, 0, 0, 0};

TwoWire I2Ctwo = TwoWire(1);

bool messageSent = false;

const int tempPin = 15;
DHT dht(tempPin, DHT11);

const int photoPin = 35;
const int magneticPin = 32;

unsigned long previousMillis = 0;
const long sensorsInterval = 100;

The setup function deals with setting the pinMode for the sensors, for the bidirectional (3.3V to 5V) logic level translator OE output enable pin, it starts the server, the I2C wire, the LCD and the DHT sensor.

void setup(void) {
  pinMode(photoPin, INPUT);
  pinMode(magneticPin, INPUT);
  
  pinMode(translatorOEPin, OUTPUT);
  digitalWrite(translatorOEPin, LOW);
  
  Serial.begin(115200);

  server.start();

  digitalWrite(translatorOEPin, HIGH);
  Serial.println("Translator output enabled. Waiting for " + String(translatorDelay / 1000) + " seconds...");
  delay(translatorDelay);
  
  I2Ctwo.begin(21, 22, 100000);
  
  lcd.start();
  dht.begin();
}

The loop function is doing 2 main jobs: it checks if the BLE server has new settings to be sent to the lcd object to be printed on the LCD 2004 screen and to be sent via I2C to the Arduino Nano board as a char array of cumulated characters (lines 4-26). It also reads the analog data from the 3 sensors and sends that data to the BLE server joined as a String (lines 28-46). That cumulated variable of the String type is then converted into a char array before sending it to the BLE server object.

void loop(void) {
  refreshSettings();
  
  if (checkIfNewValues(settings, prevSettings, size) == true) {
    if (isStopping() == true) {
      lcd.printNoSettings();
    } else {
      lcd.printSettings(settings[0], settings[1], settings[2], settings[3]);
      Serial.println("From Lcd: " + lcd.getSettingsAsString());
    }
    
    I2Ctwo.beginTransmission(I2CSlaveAddress);
    
    for (byte i = 0; i < size; i++) {
      char t[30];
      dtostrf(settings[i], 1, 0, t);
      I2Ctwo.write(t);
      if (i < size - 1) {
        I2Ctwo.write("x");
      }
      prevSettings[i] = settings[i];
    }

    I2Ctwo.endTransmission();
    
    delay(500);
  } else {
    unsigned long currentMillis = millis();

    if (currentMillis - previousMillis >= sensorsInterval) {
      previousMillis = currentMillis;
      
      int tempVal = dht.readTemperature() * 10;
      int photoVal = analogRead(photoPin);
      int magneticVal = analogRead(magneticPin);
      
      Serial.println("Photo sensor " + String(photoVal));
      Serial.println("Magnetic sensor " + String(magneticVal));
      Serial.println("Temp sensor " + String(tempVal));
      
      String cumulated = String(magneticVal) + "x" + String(photoVal) + "x" + String(tempVal);
      
      char buffer[cumulated.length() + 1];
      cumulated.toCharArray(buffer, cumulated.length() + 1);
      
      server.setSensorsValue((char*)&buffer);
    }
  }
}

The ServerBLE library

This library is built using the great work of Neil Kolban (nkolban github account). To easily understand what this library is doing, let’s take a look at the definitions from the ServerBLE.h header file. The libraries included at the beginning of the file are useful in creating the server, defining the services and the BLE characteristics used to exchange data with the BLE Client.

The public attributes and methods (lines 12-15) include of course the constructor, the start method that instantiates the server, 2 services for read and write BLE characteristics, advertises the server. The other 2 methods setSensorsValue and getSettings are available to the sketch .ino file to send and receive data from the object created with this class.

The private attributes and methods (lines 17-28) include instances of BLEServer, BLECharacteristic and BLEService one for read services and one for write services, as returned values of the two private methods createBLEReadService, createBLEWriteService.

#ifndef ServerBLE_h
#define ServerBLE_h

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

class ServerBLE {
  public:
    ServerBLE();
    void start();
    void setSensorsValue(char* buffer);
    std::string getSettings();
  private:
    BLEServer *_server;
    BLECharacteristic *_sensorsCharacteristic;
    BLECharacteristic *_settingsCharacteristic;
    BLEService *createBLEReadService(
      byte type,  
      char* serviceUUID, 
      char* characteristicUUID
    );
     BLEService *createBLEWriteService(
      byte type,  
      char* serviceUUID, 
      char* characteristicUUID
    );
};

#endif

To see the implementation of these definitions, please take a look at the ServerBLE.cpp source file here. You may notice the secret.h used to define the constants with UUIDs for services and characteristics. You can use an online service like Online UUID Generator for those universally unique identifiers.

#define SERVICE_UUID_SENSORS "<your-UUID>"
#define SERVICE_UUID_SETTINGS "<your-UUID>"

#define CHARACTERISTIC_UUID_SENSORS "<your-UUID>"
#define CHARACTERISTIC_UUID_SETTINGS "<your-UUID>"