ESP32 – BLE : Building an Echo Service GATT Server
As you would know I use ESP8266 in every possible way. It's probably the cheapest board with Wifi chip built-in. When I heard about ESP32, it was a dream come true. ESP32 packs both Wifi and BLE in one single module for less than $10. Given the size of developer fan following 8266 has, it's guaranteed that ESP32 will have a good support.
Two modes (Wifi/BLE) of connectivity gives many options for configuring an IoT device. In my future projects I am planning to use BLE for configuration and offline mobile sync. Wifi to connect to internet.
As a starting point I wrote my usual hello world and echo server. In this GATT server configuration. There is a single service with UUID c0de0001-feed-f00d-c0ff-eeb3d05ebeef
with two characteristics. Client (Eg. Phone App) can connect to this server and then write to c0de0002
. The server will respond by notifying on c0de0003
. It either replies with "world" or just echoes whatever the user has sent. I have not done connection management, multi packet etc. That's probably for the next post. In this post we will learn to set up the services/characteristics, start the advertisement, send and receive data.
- Install Arduino IDE
- Install new ESP32-BLE package for Arduino IDE. Instructions are specific to your operating system. If you already have rduino-esp3 installed then remove it and reinstall.
- Write or reuse the ESP32/Arduino Sketch
- Run it and test it using NRFConnect App
- Make changes and have fun
- I use ESP32 Dev Board (nodemcu dev board which has built in programmer) and a Micro USB cable
/* Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp Ported to Arduino ESP32 by Evandro Copercini Extended by Thejesh GN */ #include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> #include <BLE2902.h> #define SERVICE_UUID "c0de0001-feed-f00d-c0ff-eeb3d05ebeef" #define CHARACTERISTIC_UUID_TX "c0de0002-feed-f00d-c0ff-eeb3d05ebeef" #define CHARACTERISTIC_UUID_RX "c0de0003-feed-f00d-c0ff-eeb3d05ebeef" bool deviceConnected = false; bool messageReceivedComplete = false; BLECharacteristic *pCharacteristicRX; BLECharacteristic *pCharacteristicTX; int echoNumber = 0; std::string message; class EchoServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; //stop advt? Serial.println("Connected!"); }; void onDisconnect(BLEServer* pServer) { //restart advt? deviceConnected = false; Serial.println("Disconnected!"); } }; void respond(std::string send_message){ Serial.println("inside send_message"); pCharacteristicTX->setValue(send_message); pCharacteristicTX->notify(); Serial.println("sent send_message"); } //This call back happens when the android client writes class ServerReadCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue = pCharacteristic->getValue(); Serial.println("*********"); Serial.print("Received Value: "); for (int i = 0; i < rxValue.length(); i++) { Serial.print(rxValue[i]); } Serial.println(); //add to message (as of now just one packet) message = rxValue; //once you think all packets are received. As of now one packet messageReceivedComplete = true; } }; void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); BLEDevice::init("ThejEcho"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new EchoServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristicRX = pService->createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE ); pCharacteristicRX->addDescriptor(new BLE2902()); pCharacteristicRX->setCallbacks(new ServerReadCallbacks()); pCharacteristicTX = pService->createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristicTX->addDescriptor(new BLE2902()); pService->start(); BLEAdvertising *pAdvertising = pServer->getAdvertising(); pAdvertising->start(); Serial.println("Characteristic defined! Now you can read it in your phone!"); } void loop() { // some delay before running repeatedly delay(1000); // if device is connected and all packets are received, then respond if(deviceConnected && messageReceivedComplete){ messageReceivedComplete = false; if(String("hello").c_str() == message){ respond(String("world").c_str()); Serial.println("sent world"); }else{ respond(message); Serial.println("echoed"); } } }
In my ideal world; I would respond as soon as I receive the data (i.e inside ServerReadCallbacks). But I couldn't call
pCharacteristicTX->notify();
outside the loop() function. If I did It would crash. I am still debugging that part. As of now I am responding from loop() and it works quite well. I am not sure what's the best way here. If you know a better way, please leave a comment.
I am still exploring the possibilities. More posts will follow in this series.
Thanks for the great article! I’ve hit the same stumbling block of not being able to call pCharacteristicTX->notify(); from outside of the loop. Did you ever find a solution to this?