Making friends with iPhone and ESP32. Part 1. ESP Arduino Core

Just recently at WWDC2024 Apple presented Embedded Swift. According to the developers, this innovation will help us write programs for Hardware devices on “Pure Swift”. (Previously, for such perversions, we used SwiftIO)

Let's see how this technology will develop in the future, this article is about something completely different. I suggest you dive into a slightly different topic, which, in my opinion, is more useful and universal – microcontroller control from your iPhone/Mac/iPad and even Watch via BLE And Apple's Core Bluetooth.

The article is divided into 2 parts: the first is devoted to programming the ESP microcontroller, and the second is about writing a manager using Core Bluetooth.

After reading this article, you will have an idea of ​​how you can conquer the hardware worlds with your iPhone.

Spoiler! There will be no hard material / documentary etc., I will try to convey the idea in human language, with pictures: how everything works and what is happening in general.

What is this part about?

Let's look at the work of Arduino ESP Core using examples, namely:

  • Let's create our own service on the ESP32 board and connect to it from our iPhone.

  • Let's send our first commands from the iPhone to the ESP32 board.

Base

You know from school that we need some kind of device to receive and transmit information. For example, a person's device for receiving information is the eyes and ears, and for transmitting it is the speech apparatus or writing.

Things are more interesting with microcontrollers, for this they only need one module. In fact, there are several, but today we will talk specifically about Bluetooth.

Bluetooth was invented back in 1998 as a simple wireless data exchanger and since then it has undergone quite a large number of updates and changes.

One of these changes (Bluetooth 4.0) is a new version of the core specification released in 2009. Bluetooth Low Energy, which Apple immediately implemented in the iPhone 4S (the iPhone 4, by the way, still had the old Bluetooth 2.1).

The key feature of BLE is that it consumes less power* compared to classic Bluetooth, but unfortunately they are not compatible.

*This is possible due to deep protocol optimization, turning off the transmitter at the first opportunity and sending small amounts of data at low speed.

Let's get to the more interesting part.

Working with a microcontroller based on ESP32. Creating your own service.

Further in the article we will operate with several terms that are useful for us as developers to know (The following definitions will be given in the context of Bluetooth): Server, Service And Characteristic.

Server ESP32 is used to host services on it. We will also use it to handle device connection and disconnection events.

For better understanding Service And Characteristic I will give an analogy with a data structure that you have long been familiar with – Class.

To structure and transmit data, Bluetooth uses so-called services And characteristics:

Analogy between Class and Bluetooth Service

Analogy between Class and Bluetooth Service

This is the most simplified diagram:

The service must store its unique number – we will use 128-bit UUID. Like a class, a service contains properties – characteristics.

Each characteristic, in turn, necessarily stores its own unique number (we will also use a 128-bit identifier), as well as properties (Read, Write, WriteNR) and its meaning.

When sending a message from iPhone to ESP32, we write it to the value of the selected service characteristic and the board immediately receives it.

It's simple!

There are 2 human ways to write your own BLE service on ESP:

  • Using native framework ESP-IDF.

  • Using ESP Arduino Coresome kind of wrapper over ESP-IDF.

Second option much simpler and more convenient. We will consider it.

Creating a service. ESP Arduino Core.

To work we need to install Arduino IDE And Arduino ESP32 support.

One of the advantages of ESP Arduino Core is the ability to use the C++ language. The fact is that ESP-IDF itself is written in pure C, and this wrapper allows you to save a lot of time and lines of code needed to create a Bluetooth service.

Before we start writing the service, we need to understand how exactly we will receive and process the received values?

For this task, there are Callbacks functions that are used to handle various events that occur during Bluetooth operation.

Launch Arduino IDE and create a new project.

First, let's write a class MyServerCallbacks and we will inherit it from BLEServerCallbacksnow in the new class we have methods available for overriding, which are used to handle events of connecting a device to our server and disconnecting from it.

#include <BLEServer.h>

// Создание Callback функций для сервера. 
class MyServerCallbacks: public BLEServerCallbacks {
public:
  
  void onConnect(BLEServer* pServer) {
    Serial.print("iDevice was successfully connected\n");

    // Handle your logic on connect
  }

  void onDisconnect(BLEServer* pServer) {
    Serial.print("iDevice was successfully disconnected\n");

    // Handle your logic on disconnect
    pServer->startAdvertising(); // !!!
  }
  
};

Pay attention to line #17: When disconnecting a device from our board, the server must be restarted, otherwise you will not be able to connect to it again. You will have to do a hard reset on the board.

Now let's write a callback to handle messages coming from the publisher device:

class CharacteristicCallbacks: public BLECharacteristicCallbacks {
public:

  void onWrite(BLECharacteristic *pCharacteristic) {
    // Данный блок кода будет выполняться каждый раз, когда мы отправляем сообщение с iPhone на ESP
    std::string value = pCharacteristic->getValue();
    Serial.print("Message received: " + String(value.c_str()) + "\n");
  }

};

For each characteristic you need to create your own class and inherit it from BLECharacteristicCallbacks.

Method void onWrite(BLECharacteristic *pCharacteristic)serves as a callback when writing a value to a characteristic. We can get this value by calling getValue()for this characteristic (line 6).

Now we can start writing the service. Let's break our task down into 5 steps:

  1. Create a device – in order for our board to be displayed as a device, we need to give it a name on the network and initialize it.

  2. Create a server – it is on it that our service will be placed and it is to it that other devices will be connected.

  3. Create a service – several services can be hosted on one server, for each task, but for our example one will be enough.

  4. Create characteristics – one service can have several characteristics, one will be enough for us as an example.

  5. Set up Server's Advertising – public server information: Device name, UUID, etc.

Let's create a function

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

#define SERVICE_UUID                         "9A8CA9EF-E43F-4157-9FEE-C37A3D7DC12D" // ID сервиса
#define SERVICE_CHARACTERISTIC_UUID          "CC46B944-003E-42B6-B836-C4246B8F19A0" // ID характеристики

void setupBLEServer() {
  /* 1 */
  const String devName = "ESP32_BLE_Server_TEST"; // Имя, нашей платы в списке Bluetooth устройств
  BLEDevice::init(devName.c_str());  // Инициализация девайса 

  /* 2 */
  BLEServer *pServer = BLEDevice::createServer(); // Создание сервера
  pServer->setCallbacks(new MyServerCallbacks()); // Подключение Callback'а

  /* 3 */
  BLEService *pService = pServer->createService(SERVICE_UUID); // Cоздание сервиса

  /* 4 */
  BLECharacteristic *pCharacteristic; // Инициализирую характеристику для передачи сообщений
  pCharacteristic = pService->createCharacteristic(SERVICE_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
  pCharacteristic->setCallbacks(new CharacteristicCallbacks());

  pService->start();

  /* 5 */
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  BLEAdvertisementData adv;

  adv.setName(devName.c_str());
  adv.setCompleteServices(BLEUUID(SERVICE_UUID));

  pAdvertising->setAdvertisementData(adv);
  pAdvertising->setScanResponseData(adv);

  pAdvertising->start();
}

Explanations:

  1. Choose any name for your board, you can also use its Chip ID: String((uint32_t)(ESP.getEfuseMac() >> 24), HEX)

  2. After creating the server, we immediately connect the previously written class to it. MyServerCallbacks

  3. When creating a service, we pass SERVICE_UUID — a 128-bit identifier — to the initializer. You can use absolutely any sequence of characters.

  4. Here I would like to dwell in more detail: When creating a characteristic, we first pass its UUID to the initializer, then the properties:

    • BLECharacteristic::PROPERTY_READ – If a characteristic has this property, clients (e.g. smartphones or other BLE devices) can query the current value of the characteristic and receive it in response.

    • BLECharacteristic::PROPERTY_WRITE – If a characteristic has this property, clients can send data to be written to the characteristic.

    • BLECharacteristic::PROPERTY_WRITE_NR – If a characteristic has this property, clients can send data to write to the characteristic, but will not receive an acknowledgement from the server. This can be useful to speed up data transfers when confirmation is not required.

    Since we will only be sending messages from the iPhone to the ESP32, we don't need the READ and WRITE properties, but I'll leave them for the example.

  5. Class BLEAdvertising is used to store public information about the server, we will not go into details, it is enough for us to know that through it we set the display name and UUID of the server, further I will show how it will look

Now all we have left to do is call our function in the method setup()

void setup() {
  // ...
  setupBLEServer();
}

Let's look at the entire code:

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


#define SERVICE_UUID                         "9A8CA9EF-E43F-4157-9FEE-C37A3D7DC12D" // ID сервиса
#define SERVICE_CHARACTERISTIC_UUID          "CC46B944-003E-42B6-B836-C4246B8F19A0" // ID характеристики


// Создание Callback функции для сервера. 
class MyServerCallbacks: public BLEServerCallbacks {
public:

  void onConnect(BLEServer* pServer) {
    Serial.print("iDevice was successfully connected\n");

    // Handle your logic on connect
  }

  void onDisconnect(BLEServer* pServer) {
    Serial.print("iDevice was successfully disconnected\n");

    // Handle your logic on disconnect
    pServer->startAdvertising(); // !!!
  }

};


class CharacteristicCallbacks: public BLECharacteristicCallbacks {
public:

  void onWrite(BLECharacteristic *pCharacteristic) {
    // Данный блок кода будет выполняться каждый раз, когда мы отправляем сообщение с iPhone на ESP
    std::string value = pCharacteristic->getValue();
    Serial.print("Message received: " + String(value.c_str()) + "\n");
  }

};


void setupBLEServer() {
  /* 1 */
  const String devName = "ESP32_BLE_Server_TEST"; // Имя, нашей платы в списке Bluetooth устройств  //Можете использовать mac address вашей платы: String((uint32_t)(ESP.getEfuseMac() >> 24), HEX)
  BLEDevice::init(devName.c_str());  // Инициализация девайса 

  /* 2 */
  BLEServer *pServer = BLEDevice::createServer(); // Создание сервера
  pServer->setCallbacks(new MyServerCallbacks()); // Подключение Callback-а

  /* 3 */
  BLEService *pService = pServer->createService(SERVICE_UUID); // Cоздание сервиса

  /* 4 */
  BLECharacteristic *pCharacteristic; // Инициализирую характеристику для передачи сообщений
  pCharacteristic = pService->createCharacteristic(SERVICE_CHARACTERISTIC_UUID, /*BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE |*/BLECharacteristic::PROPERTY_WRITE_NR);
  pCharacteristic->setCallbacks(new CharacteristicCallbacks());

  pService->start();

  /* 5 */
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  BLEAdvertisementData adv;

  adv.setName(devName.c_str());
  adv.setCompleteServices(BLEUUID(SERVICE_UUID));

  pAdvertising->setAdvertisementData(adv);
  pAdvertising->setScanResponseData(adv);

  pAdvertising->start();
}


void setup() {
  Serial.begin(9600);

  setupBLEServer();
}


void loop() {}

That's all! Thanks! ESP Arduino Core we have created a full-fledged service in ~15 lines! Let's upload the sketch to the board and check if everything works.

For now, we will use any BLE Scanner taken from the AppStore for testing. After the sketch has loaded, we go to the application and see our board in the list of available devices:

The server name matches our devName

The server name matches our devName

Having connected to the server, we see information about the service, its UUID matches what we specified.

Once we enter the service, we see our characteristic, its UUID, properties and the current value (which is not there). By clicking on WriteWithoutResponse we will be able to send the first message.

Look at our callbacks. If you connect, send a message: “Hello, world!” and disconnect, then the log will be something like this:

Serial Monitor Log in Arduino IDE

Serial Monitor Log in Arduino IDE

You just made your iPhone compatible with ESP32 and it only took 80 lines of code! ESP Arduino Core makes our life much easier when writing Bluetooth services.

Here is the end of the first part.

In the second, we have to write our own manager using the Core Bluetooth API to finally make friends between our Apple device and the little Chinese monster.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *