- PVSM.RU - https://www.pvsm.ru -
Совсем недавно на 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, но, к сожалению, они несовместимы.
*Это возможно благодаря глубокой оптимизации протокола, выключению передатчика при первой возможности и пересылке малых объёмов данных на низкой скорости.
Приступим к более интересной части.
Далее в статье мы будем оперировать несколькими терминами, которые нам, как разработчикам полезно знать (Следующие определения будут даны в контексте Bluetooth): Server, Service и Characteristic.
Server используется ESP32 для того, чтобы размещать на нём сервисы. Мы так же будем использовать его для обработки событий подключения и отключения устройств.
Для лучшего понимания Service и Characteristic приведу аналогию с уже давно знакомой вам структурой данных — Class.
Чтобы структурировать и передавать данные, Bluetooth использует так называемые сервисы и характеристики:
Это максимально упрощённая схема:
Сервис обязательно должен хранить свой уникальный номер - мы будем использовать 128-битный UUID. Как и класс, сервис содержит в себе свойства — характеристики.
Каждая характеристика, в свою очередь, обязательно хранит свой уникальный номер (мы также будем использовать 128-битный идентификатор), а так же свойства (Read, Write, WriteNR) и своё значение.
Отправляя сообщение с iPhone на ESP32, мы записываем его в значение характеристики выбранного сервиса и плата тут же его получает.
Всё просто!
Существует 2 человеческих способа написания собственного BLE сервиса на ESP:
Используя родной фреймворк ESP-IDF.
Используя ESP Arduino Core, некой обёртки над ESP-IDF.
Второй вариант намного проще и удобнее. Его мы и рассмотрим.
Для работы нам понадобится установить 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 шагов:
Создать девайс - чтобы наша плата отображалась как устройство, нужно дать ей имя в сети и проинициализировать.
Создать сервер - именно на нём будет размещён наш сервис и именно к нему будут подключаться другие устройства.
Создать сервис - на одном сервере может быть размещено несколько сервисов, под каждую задачу, но нам для примера хватит одного.
Создать характеристики - у одного сервиса может быть несколько характеристик, нам так же хватит одной для примера.
Настроить 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();
}
Пояснения:
Выбираете любое имя для вашей платы, можете использовать также её Chip ID: String((uint32_t)(ESP.getEfuseMac() >> 24), HEX)
После создания сервера сразу подключаем к нему заранее написанный класс MyServerCallbacks
При создании сервиса, в инициализатор передаём SERVICE_UUID — 128-ти битный идентификатор. Можете использовать абсолютно любую последовательность символов.
Здесь бы хотелось остановиться поподробнее: При создании характеристики, в инициализатор передаём сначала её UUID, затем свойства:
BLECharacteristic::PROPERTY_READ - Если характеристика имеет это свойство, клиенты (например, смартфоны или другие BLE-устройства) могут запрашивать текущее значение характеристики и получать его в ответ.
BLECharacteristic::PROPERTY_WRITE - Если характеристика имеет это свойство, клиенты могут отправлять данные для записи в характеристику.
BLECharacteristic::PROPERTY_WRITE_NR - Если характеристика имеет это свойство, клиенты могут отправлять данные для записи в характеристику, но не будут получать подтверждение от сервера. Это может быть полезно для ускорения передачи данных, когда подтверждение не требуется.
Поскольку мы будем только отправлять сообщения с iPhone на ESP32, нам не нужны свойства READ и WRITE, но я оставлю их для примера.
Класс 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. После того, как скетч загрузился, заходим в приложение и видим нашу плату в списке доступных устройств:
Подключившись к серверу, мы видим информацию о сервисе, его UUID совпадает с тем, что мы указали.

Зайдя в сервис, мы видим нашу характеристику, её UUID, свойства и текущее значение (которого нет). Нажав на WriteWithoutResponse мы сможем отправить первое сообщение.
Посмотрите на наши колбэки. Если вы подключитесь, отправите сообщение: "Hello, world!" и отключитесь, то лог будет примерно таким:
Вы только что подружили свой 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
Нажмите здесь для печати.