- PVSM.RU - https://www.pvsm.ru -

Дружим iPhone и ESP32. Часть 1. ESP Arduino Core

Совсем недавно на WWDC2024 Apple представила Embedded Swift [1]. По словам разработчиков данное нововведение поможет нам писать программы для Hardware устройств на "Pure Swift". (Раньше для таких извращений мы использовали SwiftIO [2])

Посмотрим, как в дальнейшем будет развиваться данная технология, эта статья совсем о другом. Я предлагаю вам окунуться немного в другую тему, которая, на мой взгляд, более полезная и универсальная - управление микроконтроллером с вашего iPhone/Mac/iPad и даже Watch посредством Apple's Core Bluetooth [3].

Статья разделена на 2 части: первая посвящена программированию микроконтроллера ESP, а вторая - написанию менеджера с использованием Core Bluetooth.

После прочтения данной статьи вы будете иметь представление о том, как с помощью вашего iPhone можно покорять harware-миры.

Спойлер! Дальше не будет жёсткой матчасти / документалки и пр., я постараюсь человеческим языком, с картинками передать идею: как всё работает и что вообще происходит.

О чём данная часть?

Рассмотрим на примерах работу Arduino ESP Core, а именно:

  • Создадим собственный сервис на плате ESP32 и подключимся к нему с нашего iPhone.

  • Отправим наши первые команды с iPhone на плату ESP32.

База

Ещё со школьной скамьи вы знаете, что для приёма и передачи информации нам необходимо какое-то устройство. Например, у человека устройством для приёма информации служат глаза и уши, для передачи - речевой аппарат или письмо.

У микроконтроллеров с этим дела обстоят поинтереснее, для этого им достаточно всего одного модуля. На самом деле их несколько, но сегодня мы поговорим именно о Bluetooth.

Bluetooth придумали ещё в далёком 1998 году как простой беспроводной дата-обменник и с тех пор он перетерпел довольно большое количество обновлений и изменений.

Одним из таких изменений (Bluetooth 4.0) - это выпущенная в 2009 году новая версия спецификации ядра Bluetooth Low Energy, которую apple сразу же внедрила в iPhone 4S (iPhone 4, кстати, всё ещё имел старый Bluetooth 2.1).

Ключевая особенность BLE в том, что он потребляет меньше энергии* по сравнению с классическим Bluetooth, но, к сожалению, они несовместимы.

*Это возможно благодаря глубокой оптимизации протокола, выключению передатчика при первой возможности и пересылке малых объёмов данных на низкой скорости.

Приступим к более интересной части.

Работа с микроконтроллером на базе ESP32. Создание собственного сервиса.

Далее в статье мы будем оперировать несколькими терминами, которые нам, как разработчикам полезно знать (Следующие определения будут даны в контексте Bluetooth): Server, Service и Characteristic.

Server используется ESP32 для того, чтобы размещать на нём сервисы. Мы так же будем использовать его для обработки событий подключения и отключения устройств.

Для лучшего понимания Service и Characteristic приведу аналогию с уже давно знакомой вам структурой данных — Class.

Чтобы структурировать и передавать данные, Bluetooth использует так называемые сервисы и характеристики:

Аналогия между Class и Bluetooth Service

Аналогия между Class и Bluetooth Service

Это максимально упрощённая схема:

Сервис обязательно должен хранить свой уникальный номер - мы будем использовать 128-битный UUID. Как и класс, сервис содержит в себе свойства — характеристики.

Каждая характеристика, в свою очередь, обязательно хранит свой уникальный номер (мы также будем использовать 128-битный идентификатор), а так же свойства (Read, Write, WriteNR) и своё значение.

Отправляя сообщение с iPhone на ESP32, мы записываем его в значение характеристики выбранного сервиса и плата тут же его получает.

Всё просто!

Существует 2 человеческих способа написания собственного BLE сервиса на ESP:

  • Используя родной фреймворк ESP-IDF.

  • Используя ESP Arduino Core, некой обёртки над ESP-IDF.

Второй вариант намного проще и удобнее. Его мы и рассмотрим.

Создание сервиса. ESP Arduino Core.

Для работы нам понадобится установить Arduino IDE [4] и Arduino-ESP32 support [5].

Одним из преимуществ ESP Arduino Core является возможность использования языка C++. Дело в том, что сам ESP-IDF написан на чистом C, и данная обёртка позволяет экономить большое количество времени и строк кода, необходимых для создания Bluetooth сервиса.

Перед тем, как приступить к написанию сервиса, нам нужно понять, как именно мы будем принимать и обрабатывать полученные значения?

Для этой задачи существуют функции обратного вызова (Callbacks), которые используются для обработки различных событий, возникающих в процессе работы Bluetooth.

Запустите Arduino IDE [4] и создайте новый проект.

Сначала напишем класс MyServerCallbacks и унаследуем его от BLEServerCallbacks, теперь в новом классе нам доступны методы для переопределения, которые служат для обработки событий подключения устройства к нашему серверу и отключения от него.

#include <BLEServer.h>

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

    // Handle your logic on connect
  }

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

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

Обратите внимание на строку №17: При отключении устройства от нашей платы, сервер нужно перезапустить, иначе вы не сможете подключиться к нему повторно. Придётся делать hard reset на плате.

Теперь напишем callback для обработки сообщений, поступающих от устройства-издателя:

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");
  }

};

Для каждой характеристики нужно создавать свой класс и унаследовать его от BLECharacteristicCallbacks.

Метод void onWrite(BLECharacteristic *pCharacteristic), служит коллбэком при записи значения в характеристику. Мы можем получить это значение, вызвав getValue(), у этой характеристики (строчка 6).

Теперь можем приступать к написанию сервиса. Разобьём нашу задачу на 5 шагов:

  1. Создать девайс - чтобы наша плата отображалась как устройство, нужно дать ей имя в сети и проинициализировать.

  2. Создать сервер - именно на нём будет размещён наш сервис и именно к нему будут подключаться другие устройства.

  3. Создать сервис - на одном сервере может быть размещено несколько сервисов, под каждую задачу, но нам для примера хватит одного.

  4. Создать характеристики - у одного сервиса может быть несколько характеристик, нам так же хватит одной для примера.

  5. Настроить Server's Advertising - публичная информация сервера: Имя девайса, UUID и т д.

Создадим функцию

#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();
}

Пояснения:

  1. Выбираете любое имя для вашей платы, можете использовать также её Chip ID: String((uint32_t)(ESP.getEfuseMac() >> 24), HEX)

  2. После создания сервера сразу подключаем к нему заранее написанный класс MyServerCallbacks

  3. При создании сервиса, в инициализатор передаём SERVICE_UUID — 128-ти битный идентификатор. Можете использовать абсолютно любую последовательность символов.

  4. Здесь бы хотелось остановиться поподробнее: При создании характеристики, в инициализатор передаём сначала её UUID, затем свойства:

    • BLECharacteristic::PROPERTY_READ - Если характеристика имеет это свойство, клиенты (например, смартфоны или другие BLE-устройства) могут запрашивать текущее значение характеристики и получать его в ответ.

    • BLECharacteristic::PROPERTY_WRITE - Если характеристика имеет это свойство, клиенты могут отправлять данные для записи в характеристику.

    • BLECharacteristic::PROPERTY_WRITE_NR - Если характеристика имеет это свойство, клиенты могут отправлять данные для записи в характеристику, но не будут получать подтверждение от сервера. Это может быть полезно для ускорения передачи данных, когда подтверждение не требуется.

    Поскольку мы будем только отправлять сообщения с iPhone на ESP32, нам не нужны свойства READ и WRITE, но я оставлю их для примера.

  5. Класс BLEAdvertising используется для хранения публичной информации о сервере, не будем вникать в детали, нам достаточно знать, что через него мы устанавливаем отображаемое имя и UUID сервера, дальше я покажу как это будет выглядеть

Теперь всё, что нам осталось, это вызвать нашу функцию в методе setup()

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

Давайте посмотрим на весь код целиком:

#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 connectedn");

    // Handle your logic on connect
  }

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

    // 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() {}

Вот и всё! Благодаря ESP Arduino Core мы создали полноценный сервис в ~15 строчек! Давайте зальём скетч на плату и проверим, всё ли работает.

Пока для теста будем использовать любой BLE Scanner, взятый с AppStore. После того, как скетч загрузился, заходим в приложение и видим нашу плату в списке доступных устройств:

Имя сервера совпадает с нашим devName

Имя сервера совпадает с нашим devName

Подключившись к серверу, мы видим информацию о сервисе, его UUID совпадает с тем, что мы указали.

Дружим iPhone и ESP32. Часть 1. ESP Arduino Core - 3

Зайдя в сервис, мы видим нашу характеристику, её UUID, свойства и текущее значение (которого нет). Нажав на WriteWithoutResponse мы сможем отправить первое сообщение.

Посмотрите на наши колбэки. Если вы подключитесь, отправите сообщение: "Hello, world!" и отключитесь, то лог будет примерно таким:

Лог Serial Monitor в программе Arduino IDE

Лог Serial Monitor в программе Arduino IDE

Вы только что подружили свой iPhone с ESP32 и на это ушло всего 80 строчек! ESP Arduino Core сильно облегчает нам жизнь в написании Bluetooth сервисов.

Вот и конец первой части.

Во второй нам предстоит написать собственный менеджер, используя Core Bluetooth API, чтобы уже окончательно подружить наш яблочный девайс с китайским маленьким монстром.

Автор: MatoiDev

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ios/395584

Ссылки в тексте:

[1] Embedded Swift: https://www.youtube.com/watch?v=LqxbsADqDI4&t=525s

[2] SwiftIO: https://github.com/madmachineio/SwiftIO

[3] Apple's Core Bluetooth: https://developer.apple.com/documentation/corebluetooth

[4] Arduino IDE: https://www.arduino.cc/en/software

[5] Arduino-ESP32 support: https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html

[6] Источник: https://habr.com/ru/articles/840784/?utm_source=habrahabr&utm_medium=rss&utm_campaign=840784