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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

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

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

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

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

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

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

База

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

У микроконтроллеров с этим дела обстоят поинтереснее, для этого им достаточно всего одного модуля. На самом деле их несколько, но сегодня мы поговорим именно о 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 Arduino Core.

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

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

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

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

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

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

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

Обратите внимание на строку №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, затем свойства:

    Поскольку мы будем только отправлять сообщения с 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 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() {}

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

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

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

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

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

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

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

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

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

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

Источник: https://habr.com/ru/articles/840784/


Интересные статьи

Интересные статьи

Разбираемся как создать приложение внутри Telegram на примере кликера. Добавляем работу с интерфейсом Telergam и авторизацию через его Telegram аккаунт. Часть 2 из цикла TMA на KMP.
В предыдущей части мы познакомились с базовыми понятиями, характеристиками и видами фильтров. И даже собрали простой фильтр Чебышева пятого порядка за 50 рублей. Но в статье почти ничего не было ска...
Привет, Хабр! В первой части статьи о ноутбуке из 1988 года, который весит 7 кг я знакомил вас с его историей. Напомню, что девайс не включается. Делает попытки включиться, мигает светодиодами, вк...
В процессе своей профессиональной деятельности мне приходится достаточно много работать с текстовыми документами, подготавливаемыми другими лицами. Одной из задач проверки качества документов является...
Сегодня с вами на связи отдел динамического ценообразования Ситимобил. И мы начинаем серию статей о том, как мы проводим и оцениваем ценовые эксперименты внутри наше...