Могу ли я получить данные о подключенном устройстве к ESP32 по блютуз?

Мне нужна автоматическая "авторизация" на ESP32. Хотелось реализовать её с помощью привязки конкретного телефона по блютуз к ESP внутри прошивки. Например: Я подхожу в зону действия блютуз соединения ESP32, телефон автоматически подключается к этой сети, ESP32 это видит, получает mac адрес подключенного устройства и другие неизменяемые данные об устройстве если они имеются, после чего генерирует хеш данных сравнивает их с тем что хранится в памяти и если хеши одинаковые, то устанавливает флаг в значение True, при отсутствии устройств или подключении чужого устройства флаг принимает значение False


Ответы (2 шт):

Автор решения: Seffel

Поковырявшись продолжительное время в сети, нашёл для себя более удобное решение. Его реализация описана в статье: https://microkontroller.ru/esp32-projects/ispolzovanie-bluetooth-low-energy-ble-v-esp32-dlya-soedineniya-s-fitnes-brasletom/ Опубликовано 30.01.2022 автором admin-new

Спасибо автору!

p.s. Код:

#include <BLEDevice.h> //Header file for BLE 
static BLEUUID serviceUUID("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"); //Service UUID фитнес браслета, полученный с помощью приложения nRF connect 
static BLEUUID    charUUID("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"); //Characteristic  UUID фитнес браслета, полученный с помощью приложения nRF connect
String My_BLE_Address = "ff:ff:ff:ff:ff:ff"; //аппаратный    Bluetooth MAC нашего фитнес браслета,  полученный с помощью приложения nRF connect. Будет отличаться для каждого браслета 
static BLERemoteCharacteristic* pRemoteCharacteristic;
BLEScan* pBLEScan; //Name the scanning device as pBLEScan
BLEScanResults foundDevices;
static BLEAddress *Server_BLE_Address;
String Scaned_BLE_Address;
boolean paired = false; //boolean variable to togge light (логическая переменная для включения/выключения света)
 
bool connectToserver (BLEAddress pAddress){
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");
    // Connect to the BLE Server. (соединение с BLE сервером)
    pClient->connect(pAddress);
    Serial.println(" - Connected to fitnessband");
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService != nullptr)
    {
      Serial.println(" - Found our service");
      return true;
    }
    else
    return false;
    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic != nullptr)
      Serial.println(" - Found our characteristic");
      return true;
}
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks 
{
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.printf("Scan Result: %s \n", advertisedDevice.toString().c_str());
      Server_BLE_Address = new BLEAddress(advertisedDevice.getAddress());
      
      Scaned_BLE_Address = Server_BLE_Address->toString().c_str();
      
    }
};
void setup() {
    Serial.begin(115200); //инициализируем последовательную связь 
    Serial.println("ESP32 BLE Server program"); //печатаем приветственное сообщение
    BLEDevice::init("");
    pBLEScan = BLEDevice::getScan(); //начинаем новое сканирование
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); //Call the class that is defined above 
    pBLEScan->setActiveScan(true); // активное сканирование потребляет больше мощности, но быстрее получает результат
    pinMode (13,OUTPUT); //задаем режим работы для контакта на вывод данных 
}
void loop() {
  foundDevices = pBLEScan->start(5); // сканируем в течение 5-х секунд чтобы найти фитнес браслет 
  while (foundDevices.getCount() >= 1)
  {
    if (Scaned_BLE_Address == My_BLE_Address && paired == false)
    {
      Serial.println("Found Device :-)... connecting to Server as client");
       if (connectToserver(*Server_BLE_Address))
      {
      paired = true;
      Serial.println("********************LED turned ON************************");
      digitalWrite (13,HIGH);
      break;
      }
      else
      {
      Serial.println("Pairing failed");
      break;
      }
    }
    
    if (Scaned_BLE_Address == My_BLE_Address && paired == true)
    {
      Serial.println("Our device went out of range"); // устройство вне диапазона действия
      paired = false;
      Serial.println("********************LED OOOFFFFF************************");
      digitalWrite (13,LOW);
      ESP.restart();
      break;
    }
    else
    {
    Serial.println("We have some other BLe device in range"); //некоторые BLe устройства в зоне нашего действия
    break;
    }
  } 
}

Пояснение: Основная идея программы для модуля ESP32 состоит в том, чтобы сделать его Bluetooth клиентом, который будет постоянно сканировать эфир на наличие Bluetooth устройств и когда он будет находить наш сервер (фитнес браслет), он будет проверять его аппаратный идентификатор (hardware ID) и если он правильный, то он будет включать лампу при помощи подачи соответствующего сигнала на свой контакт 13.

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

Для уменьшения радиуса действия сервера BLE мы будем использовать опцию связывания/"спаривания" (pairing option). BLE сервер и BLE клиент считаются связанными ("спаренными") только когда расстояние между ними не превышает 3-4 метров. А это расстояние уже хорошо подходит для нашего проекта. То есть в программе для модуля ESP32 мы будем выполнять не только сканирование пространства на наличие BLE сервера, мы также будем соединяться с ним и "спариваться" с ним (paired). Пока наши BLE сервер и клиент будут оставаться "спаренными" лампа переменного тока будет гореть. Как только расстояние между ними будет превышать радиус "спаривания" лампа будет выключаться.

Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.

После подключения в коде программы необходимых библиотек мы указываем в программе адрес BLE, Service и characteristic UUID, которые мы ранее получили с помощью приложения nRF connect. Далее внутри функции setup мы инициализируем последовательную связь и включаем BLE в модуле ESP на сканирование эфира. После того как сканирование будет завершено для каждого обнаруженного устройства будет вызываться функция MyAdvertisedDeviceCallbacks.

Активное сканирование эфира мы будем использовать когда мы будем запитывать модуль ESP32 от стационарного источника питания, при питании от батарейки мы будем его отключать в целях экономии энергопотребления. Контакт trigger реле подключен к контакту 13 модуля, поэтому для этого контакта мы установим режим работы на вывод данных. Внутри функции MyAdvertisedDeviceCallbacks мы будем выводить в окно монитора последовательной связи имя и другую информацию о BLE устройствах, которые мы обнаружили. Нам необходим будет аппаратный идентификатор (hardware ID) обнаруженного устройства – его мы будем сравнивать с заранее определенным значением (то есть с тем, которое нам нужно). Таким образом, мы будем использовать переменную Server_BLE_Address для получения адреса устройства и затем преобразовывать тип BLEAddress в строку. Внутри функции loop мы будем производить сканирование эфира в течение 3-х секунд и помещать результат сканирования в объект foundDevices, который является объектом класса BLEScanResults. Если мы обнаружим одно или более устройств в процессе сканирования, мы будем проверять соответствие BLE адресов этих устройств тому адресу, который нам нужен. Если адреса совпадают, но устройства еще не "спарены" ранее (not paired), мы будем пытаться их "спарить" с помощью функции connectToserver. Также часть информации мы будем выводить в окно монитора последовательной связи в целях отладки. Внутри функции connectToserver мы будем использовать UUID для связывания/"спаривания" наших BLE сервера и клиента. В качестве клиента у нас выступает модуль ESP32, поэтому мы создаем его в программе с помощью функции createClient() и затем соединяемся с адресом BLE сервера. Далее мы осуществляем поиск служб (service) и характеристик (characteristic) используя значения UUID и пытаемся осуществить соединение. Когда соединение будет успешно установлено функция будет возвращать true, а если соединение не будет установлено, то оно будет возвращать false. Заметьте, что не обязательно знать service и characteristic UUID чтобы произвести "спаривание" с сервером, мы здесь это использовали в образовательных целях. Если соединение будет успешно установлено, то на контакт 13 мы будем подавать уровень high и управление мы будем передавать за пределы функции loop используя команду break. Логическая переменная paired при этом будет устанавливаться в true. После того как "спаривание" (pairing) устройств прошло успешно и контакт 13 установлен в high, нам необходимо будет проверять находиться ли устройство в радиусе нашего действия. Поскольку устройства у нас уже "спарены", то сканирование BLE устройств уже не сможет увидеть устройство. Мы сможем найти его с помощью сканирования только когда пользователь с браслетом покинет область нашего действия. Таким образом, нам будет необходимо проверять этот факт и если мы обнаружим что BLE сервер находится за пределами области действия нашего модуля ESP32, мы будем устанавливать контакт 13 в low, а переменной paired присваивать значение false.

→ Ссылка
Автор решения: Seffel

Переработав код выше получилось его достаточно сильно упростить и ускорить. Редакцию с комментариями прилагаю:

#include <BLEDevice.h>

#define out_pin 22 // Пин светодиода на плате
#define time_sleep 3 // Время сканирования устройств
#define level_RSSI -75 // Уровень сигнала при достижении которого плата будет считать что браслет вне зоны доступа

String My_BLE_Address = "ff:ff:ff:ff:ff:ff"; //Mac-адрес фитнес браслета

BLEScan* pBLEScan;
BLEScanResults *foundDevices;
static BLEAddress *Server_BLE_Address;
String Scaned_BLE_Address;
String Scaned_BLE_RSSI;

boolean paired = false;

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks 
{
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      //Serial.printf("Scan Result: %s \n", advertisedDevice.toString().c_str()); // Вывод списка обнаруженных устройств со сведениями
      Server_BLE_Address = new BLEAddress(advertisedDevice.getAddress());
      Scaned_BLE_Address = Server_BLE_Address->toString().c_str();
      Scaned_BLE_RSSI    = advertisedDevice.getRSSI();
      if (Scaned_BLE_Address == My_BLE_Address && Scaned_BLE_RSSI.toInt() > level_RSSI) { 
        //Serial.println(Scaned_BLE_RSSI); // Вывод текущего уровня сигнала если он не превышает лимит
        paired = true; 
      }
    }
};
void setup() {
    Serial.begin(115200);
    BLEDevice::init("SonZMoon");
    pBLEScan = BLEDevice::getScan();
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
    pBLEScan->setActiveScan(true);
    pinMode (out_pin, OUTPUT);
    digitalWrite (out_pin, HIGH);
}
void loop() {
  paired = false;
  foundDevices = pBLEScan->start(time_sleep);
  if (paired == true) {
    digitalWrite (out_pin, LOW);
  } else {
    digitalWrite (out_pin, HIGH);
  }
}
→ Ссылка