Датчики приближения (proximity sensors) предназначены для обнаружения близкорасположенных объектов с помощью света, электромагнитного поля или звука. В ряду случаев для обнаружения близко расположенных объектов целесообразно использовать технологию BLE (Bluetooth Low Energy – Bluetooth с низким энергопотреблением). Для создания подобного датчика можно использовать модуль ESP32, поскольку он имеет встроенную поддержку BLE.
В данной статье мы рассмотрим создание детектора BLE устройств на основе модуля ESP32. Ранее на нашем сайте мы рассматривали использование технологии BLE в ESP32 для соединения с фитнес браслетом.
Необходимые компоненты
- Модуль ESP32 (купить на AliExpress).
- Смартфон.
- Умные часы.
Внешний вид компонентов, необходимых для нашего проекта, показан на следующем рисунке.
Основные принципы работы технологии BLE
BLE (Bluetooth Low Energy) – это Bluetooth с низким потреблением. Технология BLE была разработана в 2011 г. и с тех пор находит широкое применение для связи на короткие расстояния в устройствах, получающих питание от батареек/аккумуляторов: смартфоны, умные часы, беспроводные гарнитуры, беспроводные колонки и т.п.
Технология BLE была разработана специальной группой по интересам (Special Interest Group, SIG) Bluetooth. Основной целью создания технологии было максимальное снижение энергопотребления устройств. Хотя в названии технологии BLE есть слово "Bluetooth", тем не менее, данная технология не обладает обратной совместимостью, то есть классические Bluetooth устройства не могут осуществлять прием данных от устройств BLE. Но зато это позволило разработчикам создать технологию, благодаря которой беспроводные устройства могут работать от крошечной батарейки несколько месяцев (или даже лет).
Технология BLE использует иерархическую структуру данных для передачи и приема информации. BLE устройство, работающее в качестве сервера, предоставляет услуги и характеристики, которые могут быть обнаружены клиентом. И как только произойдет обмен информацией между клиентом и сервером, эти два устройства смогут работать друг с другом одновременно. Этот стек (совокупность) информации является атрибутом (отличительным свойством) BLE устройства. Данная информация задается с помощью профиля GATT (Generic Attributes). Данный профиль содержит услуги/службы (Service), характеристики (Characteristics) и значения (values) в иерархическом порядке. Службы содержат характеристики, а характеристики содержат значения. Считывая характеристики, мы можем считывать их значения и изменять их.
Структура GATT профиля представлена на следующем рисунке.
Характеристики (Characteristics) могут быть использованы для чтения или записи информации. Устройства, содержащие компоненты для чтения, могут публиковать (publish) информацию, а устройства, содержащие компоненты для записи, могут принимать данные от клиента.
GATT профиль технологии BLE также известен как UUID (Universally Unique Identifier – универсальный уникальный идентификатор). Существует ряд стандартных служб и характеристик, определенных корпорацией SIG. Если мы считываем UUID BLE устройства, мы сразу можем определить тип этого устройства.
Данные BLE передаются и принимаются с помощью очень коротких пакетов. Пакет BLE содержит всего 31 байта, в то время как, например, пакет протокола TCP может содержать более 60 байт. Важным правилом при работе с технологией BLE является то, что ее пакеты должны иметь строго определенную структуру и байты этих пакетов должны передаваться и приниматься последовательно.
Как работает датчик приближения на основе технологии BLE
Как уже говорилось ранее, датчики приближения могут обнаруживать близко расположенные объекты с помощью света, электромагнитного поля или звука.
BLE серверы регулярно передают в эфир сигналы приветствия (advertisement signals), поэтому клиенты могут обнаружить эти сигналы и, таким образом, подключиться к серверам. Эти сигналы приветствия содержат уникальный BLE MAC (Media Access Control) адрес, который очень похож на MAC адрес, используемый в технологии Wi-Fi. Поскольку модуль ESP32 имеет встроенную поддержку Bluetooth, с его помощью мы можем легко обнаружить эти транслируемые сигналы приветствия и сравнить их с таблицей соответствия чтобы обнаружить присутствие необходимого нам устройства.
Когда устройство обнаружено и проверено на соответствие адреса, мы можем, к примеру, включить светодиод, подключенный к модулю ESP32, или с помощью сервиса Adafruit IO отправить уведомление на смартфон с операционной системой android.
Объяснение программы для модуля ESP32
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
В целях демонстрации работы проекта мы запрограммируем модуль ESP32 на обнаружение известных BLE маяков (beacons) и когда мы будем обнаруживать такой маяк, мы будем зажигать встроенный в модуль ESP32 светодиод.
Первым делом в начале программе мы подключим используемые библиотеки и объявим необходимые переменные. Поскольку мы будем использовать класс BLEScan, то в программе мы должны подключить соответствующую библиотеку для работы с ним.
1 2 3 4 |
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEScan.h> #include <BLEAdvertisedDevice.h> |
Далее объявим ряд необходимых переменных, первой из которых будет массив, в котором мы будем хранить известные BLE MAC адреса. Далее мы объявим значение границы (минимальный уровень сигнала), при превышении которой будет фиксироваться факт обнаружения устройства и выполняться необходимое действие. Далее объявим переменную логического типа (Boolean) которую мы будем устанавливать в true если одно из просканированных (обнаруженных) устройств содержится в нашем массиве с известными адресами. Также мы объявим еще одну переменную чтобы очищать уже просканированные BLE устройства, если мы не будем делать этого, то рано или поздно возникнут проблемы с переполнением памяти. И еще мы объявим указатель на класс BLEScan.
1 2 3 4 5 |
String knownBLEAddresses[] = {"aa:bc:cc:dd:ee:ee", "54:2c:7b:87:71:a2"}; int RSSI_THRESHOLD = -55; bool device_found; int scanTime = 5; //In seconds BLEScan* pBLEScan; |
Также мы запрограммируем функцию обратного вызова (call-back function), сначала эта функция будет вызываться каждые несколько секунд чтобы проверить присутствуют ли в зоне действия нашего модуля новые BLE устройства или нет. Внутри этой функции при обнаружении BLE устройства мы будем устанавливать соответствующий флаг и осуществлять выход из цикла. И наконец, мы будем выводить на экран информацию об обнаруженном BLE устройстве.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { for (int i = 0; i < (sizeof(knownBLEAddresses) / sizeof(knownBLEAddresses[0])); i++) { //Uncomment to Enable Debug Information //Serial.println("*************Start**************"); //Serial.println(sizeof(knownBLEAddresses)); //Serial.println(sizeof(knownBLEAddresses[0])); //Serial.println(sizeof(knownBLEAddresses)/sizeof(knownBLEAddresses[0])); //Serial.println(advertisedDevice.getAddress().toString().c_str()); //Serial.println(knownBLEAddresses[i].c_str()); //Serial.println("*************End**************"); if (strcmp(advertisedDevice.getAddress().toString().c_str(), knownBLEAddresses[i].c_str()) == 0) { device_found = true; break; } else device_found = false; } Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); } }; |
Далее в функции setup мы зададим режим работы контакта, к которому подключен светодиод, на вывод данных. Также мы инициализируем BLE устройство (BLEDevice) с помощью метода BLEDevice::init, после этого мы получим просканированный объект от нашего BLEDevice, мы сохраним этот адрес в ранее объявленном указателе на pBLEScan.
Затем мы инициализируем функцию обратного вызова – она будет вызываться каждые несколько секунд чтобы проверить доступны ли новые устройства или нет. Далее мы используем метод SetActiveScan чтобы установить активный режим сканирования – данный режим потребляет больше энергии, но позволяет быстрее получить результаты сканирования. Также установим интервал и окно сканирования с помощью методов setInterval и setWindow.
1 2 3 4 5 6 7 8 9 |
Serial.begin(115200); //Enable UART on ESP32 Serial.println("Scanning..."); // Print Scanning pinMode(LED_BUILTIN, OUTPUT); // BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); //create new scan pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); //Init Callback Function pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster pBLEScan->setInterval(100); // set Scan interval pBLEScan->setWindow(99); // less or equal setInterval value |
В функции loop мы запустим обнаружение BLE устройств. Параметр scanTime определяет в течение какого времени будет производиться сканирование новых устройств.
1 |
BLEScanResults foundDevices = pBLEScan->start(scanTime, false); |
Когда процесс сканирования будет завершен, мы сможем узнать количество найденных устройств с помощью метода devices.getCount(). Мы поместим вызов этого метода в цикл. В данном цикле мы будем проверять уровень сигнала (RSSI) от каждого обнаруженного устройства. После этого мы будем сравнивать значение уровня сигнала с ранее заданной границей и проверять находится ли устройство в нашем списке или нет. Если мы обнаружим известное нам устройство (оно есть в нашем списке), то мы будем включать встроенный в модуль ESP32 светодиод, иначе мы будем выключать светодиод.
1 2 3 4 5 6 7 8 9 10 11 |
for (int i = 0; i < foundDevices.getCount(); i++) { BLEAdvertisedDevice device = foundDevices.getDevice(i); int rssi = device.getRSSI(); Serial.print("RSSI: "); Serial.println(rssi); if (rssi > RSSI_THRESHOLD && device_found == true) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); } |
Затем мы будем удалять результаты сканирования чтобы очистить память.
1 |
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory |
После того как вы загрузите код программы в модуль ESP32, вы сможете с его помощью обнаруживать известные BLE устройства, когда они находятся рядом с вами. Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEScan.h> #include <BLEAdvertisedDevice.h> String knownBLEAddresses[] = {"6E:bc:55:18:cf:7b", "53:3c:cb:56:36:02", "40:99:4b:75:7d:2f", "5c:5b:68:6f:34:96"}; int RSSI_THRESHOLD = -55; bool device_found; int scanTime = 5; //в секундах BLEScan* pBLEScan; class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { for (int i = 0; i < (sizeof(knownBLEAddresses) / sizeof(knownBLEAddresses[0])); i++) { //Uncomment to Enable Debug Information //Serial.println("*************Start**************"); //Serial.println(sizeof(knownBLEAddresses)); //Serial.println(sizeof(knownBLEAddresses[0])); //Serial.println(sizeof(knownBLEAddresses)/sizeof(knownBLEAddresses[0])); //Serial.println(advertisedDevice.getAddress().toString().c_str()); //Serial.println(knownBLEAddresses[i].c_str()); //Serial.println("*************End**************"); if (strcmp(advertisedDevice.getAddress().toString().c_str(), knownBLEAddresses[i].c_str()) == 0) { device_found = true; break; } else device_found = false; } Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); } }; void setup() { Serial.begin(115200); //Enable UART on ESP32 Serial.println("Scanning..."); // Print Scanning pinMode(LED_BUILTIN, OUTPUT); //режим работы контакта BUILTIN_LED на вывод данных BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); //создаем новое сканирование pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); //Init Callback Function (инициализируем функцию обратного вызова) pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster pBLEScan->setInterval(100); // устанавливаем интервал сканирования pBLEScan->setWindow(99); // устанавливаем окно сканирования, менее или равно значению setInterval } void loop() { // put your main code here, to run repeatedly: BLEScanResults foundDevices = pBLEScan->start(scanTime, false); for (int i = 0; i < foundDevices.getCount(); i++) { BLEAdvertisedDevice device = foundDevices.getDevice(i); int rssi = device.getRSSI(); Serial.print("RSSI: "); Serial.println(rssi); if (rssi > RSSI_THRESHOLD && device_found == true) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); } pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory } |