Začínáme s ESP32 Bluetooth Low Energy (BLE)
- UPOZORNĚNÍ
- Následující článek byl již upraven pro jádro Arduino ESP32 verze 3.x.
V minulém článku věnovaném Bluetooth jsme si ukázali základní použití Bluetooth Classic v modulu ESP32. Dnes bychom se rádi podívali na tzv. Bluetooth Low Energy (BLE). Nejprve v rychlosti si trochu zopakujeme, co je BLE a k čemu se dá použít. Poté se podíváme na několik příkladů s modulem ESP32, který budeme programovat pomocí Arduino IDE. Nakonec bychom si měli z modulu ESP32 vytvořit jednoduchý BLE server, ale také i BLE skener, který předchozí server najde.
Co je Bluetooth Low Energy?
Bluetooth Low Energy alias BLE (dříve uváděný na trh jako Bluetooth Smart) je vysoce optimalizovaná verze klasického Bluetooth navržená speciálně pro bezdrátovou komunikaci s nízkou spotřebou. Primárním účelem BLE je přenos malého množství dat na krátkou vzdálenost (nízká šířka pásma). Na rozdíl od Bluetooth, které je vždy zapnuté, zůstává BLE v režimu spánku, ze kterého se probudí, jen když je zahájeno připojení. Díky tomu má velmi nízkou spotřebu energie. BLE spotřebuje přibližně 100× méně energie než Bluetooth (v závislosti na případu použití). BLE navíc podporuje nejen komunikaci typu point-to-point, ale také režim vysílání a síť typu Mesh.
Zjednodušeně řečeno, každé zařízení Mesh sítě je kromě své funkce zároveň routerem, který může přenášet informace od ostatních zařízení. To zvyšuje spolehlivost sítě a její dosah. Zatímco standardní BT vysílání dosáhne řádově deseti metrů, Bluetooth Low Energy desítek metrů, s pomocí síťového propojení Mesh se reálný dosah pohybuje ve stovkách metrů, pokud jsou pokryty zařízeními schopnými předávat informace.
Díky svým vlastnostem je BLE vhodný pro aplikace, které potřebují vyměňovat malé objemy dat, například využití ve zdravotnictví, ve fitness náramcích, v zabezpečovací technice a obecně v domácí automatizaci.
BLE
S Bluetooth Low Energy existují dva typy zařízení: server a klient. Modul ESP32 pochopitelně může fungovat buď jako klient nebo jako server. Server inzeruje svou existenci (tzv. advertising), takže jej mohou najít jiná zařízení a obsahuje data, která může klient číst. Klient prohledá okolní zařízení, a když najde hledaný server, naváže spojení a naslouchá příchozím datům. Tomu se říká komunikace point-to-point (česky též tzv. dvoubodový spoj).
Obrázek 1 – princip komunikace Point-to-Point. Zdroj: *)
Jak již bylo zmíněno dříve, BLE také podporuje režim vysílání a Mesh síť:
- Režim vysílání: server přenáší data mnoha klientům, kteří jsou připojeni
- Mesh síť: všechna zařízení jsou připojena, jedná se o mnohonásobné připojení.
Sítě Mesh byly vyvinuty velmi nedávno, takže v tuto chvíli není mnoho příkladů implementovaných pro modul ESP32, ale budeme se snažit něco pro Vás najít a vyzkoušet. Pokud již nějaký takový příklad máte, budeme vděčni, když se s námi o své zkušenosti podělíte.
GATT
GATT je zkratka pro Generic Attributes a definuje hierarchickou datovou strukturu, která je vystavena připojeným zařízením BLE. To znamená, že GATT definuje způsob, jakým dvě zařízení BLE odesílají a přijímají standardní zprávy. Pochopení této hierarchie je důležité, protože vám usnadní pochopení toho, jak používat BLE a psát své aplikace.
Obrázek 2 – Hierarchická datová struktura GATT. Zdroj: *)
Služba BLE
Nejvyšší úrovní hierarchie je profil, který se skládá z jedné nebo více služeb. Zařízení BLE obvykle obsahuje více než jednu službu. Každá služba obsahuje alespoň jednu charakteristiku, případně může odkazovat i na jiné služby. Služba je jednoduše sbírka informací, jako jsou například údaje ze senzorů. Existují předdefinované služby pro několik typů dat definovaných SIG (Bluetooth Special Interest Group), jako jsou: stav baterie, krevní tlak, srdeční frekvence, hmotnost atd.
Obrázek 3 – Několik typů dat definovaných SIG. Zdroj: *)
Charakteristika BLE
Charakteristiku vždy určuje daná služba a je to místo, kde jsou v hierarchii (hodnotě) obsažena skutečná data. Charakteristika má vždy dva atributy: deklaraci charakteristiky (která poskytuje metadata o datech) a hodnotu charakteristiky. Navíc za charakteristickou hodnotou mohou následovat deskriptory, které dále rozšiřují metadata obsažená v deklaraci charakteristiky. Vlastnosti popisují, jak může být charakteristická hodnota ovlivněna. V zásadě obsahuje operace a postupy, které lze použít s charakteristikou:
- Broadcast (Přenos)
- Read (Čtení)
- Write without response (Zápis bez odezvy)
- Write (Zápis)
- Notify (Oznámení)
- Indicate (Označení)
- Authenticated Signed Writes (Ověřené zápisy)
- Extended Properties (Rozšířené vlastnosti)
UUID
Každá služba, charakteristika a deskriptor mají UUID (Universally Unique Identifier). UUID je jedinečné 128bitové (16 bajtové) číslo.
Například:
55072829-
Existují zkrácené UUID pro všechny typy, služby a profily specifikované v SIG (Bluetooth Special Interest Group). Pokud však naše aplikace potřebuje vlastní UUID, můžeme jej vygenerovat pomocí této webové stránky generátoru UUID. Stručně řečeno, UUID se používá pro jednoznačnou identifikaci informací. Může například identifikovat konkrétní službu poskytovanou zařízením Bluetooth.
BLE a modul ESP32
ESP32 může fungovat jako BLE server nebo jako BLE klient. Původně externí knihovna ESP32_BLE_Arduino, kterou budeme dále používat, je již součástí prostředí Arduino IDE, takže tuto knihovnu ani nemusíme do prostředí Arduino IDE dodatečně instalovat. K dispozici máme i několik ukázkových programů – pokud se na ně chcete podívat, přejděte ve svém prostředí Arduino IDE na Soubor > Příklady > ESP32 BLE Arduino.
Obrázek 4 – Nalezení ukázkových kódů k problematice BLE (v prostředí Arduino IDE).
- Poznámka:
- Chcete-li vidět příklady pro modul ESP32, musíte mít desku s ESP32 vybranou v nabídce Nástroje > Vývojová deska.
Modul ESP32 jako BLE Server
Při vytvoření BLE serveru by měly v kódu našeho programu následovat následující kroky:
- Vytvořit objekt BLE serveru.
- Vytvořit službu BLE.
- Vytvořit charakteristiku BLE pro službu.
- Vytvořit deskriptor BLE pro charakteristiku.
- Spustit službu.
- Začít se „inzerovat“ (advertising), aby jej mohla najít jiná zařízení.
Nevypadá to jednoduše, co? Tak raději pro začátek použijeme něco již hotového a postupně si kód vysvětlíme. Pro náš účel použijeme základní ukázkový program BLE_server. Otevřeme prostředí Arduino IDE a přejděte na Soubor > Příklady > ESP32 BLE Arduino a vyberte příklad BLE_serveru.
Měl by se načíst následující kód:
/*
Based on Neil Kolban example for IDF:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
Ported to Arduino ESP32 by Evandro Copercini
updates by chegewara
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
BLEDevice::init("MyESP32"); // Long name works now
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setValue("Hello World says Neil");
pService->start();
// BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
Jak kód funguje
Pojďme se tedy podívat na to, jak tento ukázkový kód serveru BLE funguje.
Začíná importem knihoven nezbytných pro funkce BLE.
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
Poté musíte definovat UUID pro službu a charakteristiku.
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
UUID můžeme ponechat výchozí, nebo můžeme přejít na uuidgenerator.net a vytvořit náhodná UUID pro naše služby a vlastnosti.
V proceduře setup() se kromě nastavení sériového portu vytvoří objekt zařízení BLE s názvem „MyESP32“. (Tento název si můžeme změnit na jakýkoli jiný).
// Create the BLE Device
BLEDevice::init("MyESP32");
Na následujícím řádku se zařízení BLE nastaví jako server.
BLEServer *pServer = BLEDevice::createServer();
Poté se vytvoří služba pro server BLE s dříve definovaným UUID.
BLEService *pService = pServer->createService(SERVICE_UUID);
V dalším kroku musíme nastavit charakteristiku pro tuto službu. Používáme UUID definovanou dříve a jako argumenty musíte předat vlastnosti charakteristiky. V tomto případě to jsou: ČTENÍ; a ZÁPIS.
BLECharacteristic *pCharacteristic =
pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
Po vytvoření charakteristiky můžete nastavit její hodnotu pomocí metody setValue().
pCharacteristic->setValue("Hello World says Neil");
V tomto případě nastavujeme hodnotu na text „Hello World says Neil“. Tento text opět můžeme změnit na svůj libovolný. V budoucích projektech může být tímto textem například čtení senzoru nebo stav LED.
Konečně můžete spustit službu a „inzerci“ (advertising), takže ostatní zařízení BLE mohou toto zařízení BLE skenovat a najít.
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Toto je jen jednoduchý příklad, jak vytvořit BLE server. V tomto kódu se nic nedělá, proto je hlavní smyčka loop() prázdná, ale můžete přidat, co se má stát, když se připojí nový klient (pokyny najdete v příkladu BLE_notify).
Testování BLE serveru na modulu ESP32 pomocí smartphonu
Většina moderních smartphonů dnes již BLE funkce podporuje, takže náš BLE server na modulu ESP32 by se měl dát chytrým telefonem naskenovat a prohlédnout si jeho služby a vlastnosti. K tomu využijeme bezplatnou aplikaci s názvem nRF Connect for Mobile od společnosti Nordic® Semiconductor. Tato aplikace je dostupná pro telefony jak s operačním systémem Android (odkaz na Google Play Store), tak systémem iOS (odkaz na App Store).
Obrázek 5 – Aplikace nRF Connect for Mobile na smartphonu s operačním systémem Android.
Přejdeme tedy do obchodu Google Play nebo App Store, vyhledáme a nainstalujeme aplikaci nRF Connect for Mobile.
Po spuštění aplikace nezapomeňte přejít do nastavení Bluetooth a povolit adaptér Bluetooth. Můžete jej také zviditelnit pro ostatní zařízení, abyste mohli později testovat další programy.
Obrázek 6 – První nastavení aplikace nRF Connect for Mobile.
Jakmile je vše připraveno ve smartphonu, spustíme v modulu ESP32 program BLE_server. V pravém horním rohu okna aplikace klikneme na tlačítko SCAN a mobil začne vyhledávat zařízení v okolí. Měli bychom mimo jiného najít i modul ESP32 s programem BLE server, pojmenovaný MyESP32.
Obrázek 7 – Spárování smartphonu se BLE serverem na modulu ESP32.
U našeho ESP32 BLE serveru (MyESP32) klikneme na tlačítko CONNECT.
Jak můžete vidět na obrázku níže, ESP32 má službu s UUID, které jste definovali dříve. Pokud klepneme na službu (Unknown Service), popřípadě klepneme na nabídku Read characteristics (v menu tří teček v šedé liště), zobrazí se charakteristika s UUID, které jste také definovali.
Obrázek 8 – Výpis služby BLE poskytované BLE serverem.
Charakteristika má vlastnosti READ a WRITE a hodnota je hodnota, kterou jste dříve definovali v náčrtu serveru BLE. Takže vše funguje dobře.
Modul ESP32 jako BLE skener
Pokud jsme pochopili princip předchozího příkladu, mělo by být vytvoření BLE skeneru poměrně jednoduché. Abychom měli co skenovat, necháme předchozí program běžet na jednom modulu ESP32 a vezmeme další vývojovou deku s ESP32 a naprogramujeme ji jako BLE skener. V prostředí Arduino IDE přejděte na Soubor > Příklady > ESP32 BLE Arduino a vyberte příklad BLE_scan.
Měl by se načíst následující kód:
/*
Based on Neil Kolban example for IDF:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
int scanTime = 5; // In seconds
BLEScan *pBLEScan;
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); //less or equal setInterval value
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices->getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
Tento kód inicializuje modul ESP32 jako zařízení BLE a vyhledá okolní zařízení. Vyzkoušíme ho tedy tak, že jej nahrajeme do druhého modulu ESP32. (Možná nebude od věci, dočasně odpojit druhý ESP32 od počítače, abychom si byli jisti, že nahráváme kód na správnou vývojovou desku s modulem ESP32. 😉).
Jakmile je kód nahrán a měli byste mít zapnuté dvě desky ESP32:
- Jednu vývojovou desku s modulem ESP32, která je naprogramována jako
BLE_server, - Druhou vývojovou desku s programem
BLE_scan.
Přejdeme na sériový monitor modulu ESP32 se spuštěným programem BLE_scan, tento modul restartujeme (tlačítko EN, resp. RST). Po restartu počkáme několik sekund, než proběhne skenování BLE okolních zařízení.
Obrázek 9 – Vyhledávání BLE serveru na modulu ESP32 pomocí BLE skeneru na druhém ESP32
Po přeložení kódu, jeho nahrání do modulu ESP32 bychom měli v okně sériového monitoru vidět výstup podobný následujícímu obrázku.
Obrázek 10 – Výstup BLE skeneru - seznam nalezených zařízení
Na obrázku č. 10 dokonce vidíme, že náš BLE skener nalezl nejen modul ESP32, na kterém běží BLE server, ale i fitness náramek, který byl kdesi v blízkosti. 😎
Praktická část
Prohlédnutí ukázkových programů z nabídky prostředí Arduino IDE je dobrý začátek, protože nám pomůže pochopit základní principy Bluetooth Low Energy – tedy vytvoření BLE serveru, služby, charakteristiky a komunikaci mezi modulem ESP32 a klientem (například chytrým telefonem). Samotné studium hotových příkladů ale většinou nestačí. V reálném projektu potřebujeme pomocí BLE ovládat konkrétní hardware nebo naopak z modulu ESP32 odesílat skutečná data. Právě tehdy se ukáže, jak jednotlivé části BLE spolu souvisejí a jak je použít ve vlastním programu.
V následujících dvou jednoduchých, ale praktických příkladech si ukážeme oba nejběžnější směry komunikace:
- telefon → ESP32: z telefonu budeme posílat příkazy a pomocí nich rozsvěcet nebo zhasínat vestavěnou LED na modulu,
- ESP32 → telefon: modul bude odesílat informaci o stavu tlačítka
BOOTa telefon bude tento stav zobrazovat.
První příklad vychází především z ukázkového programu BLE_server. Zachováme stejnou základní strukturu BLE serveru, ale místo textu typu „Hello World“ budeme reagovat na data přijatá z telefonu a podle nich ovládat LED.
PŘÍKLAD č. 1: Program – ovládání LED
Cílem tohoto příkladu je vytvořit jednoduché bezdrátové ovládání LED. Modul ESP32 bude vystupovat jako BLE server a chytrý telefon jako BLE klient. Po připojení telefonu budeme do charakteristiky zapisovat hodnotu:
- „1“ – LED se rozsvítí,
- „0“ – LED zhasne.
Program tedy nebude pouze „vysílat nějaký text“, ale bude vykonávat konkrétní akci – měnit stav vestavěné LED. Díky tomu uvidíme, jak lze pomocí BLE velmi jednoduše realizovat základní prvky dálkového ovládání, které jsou základem mnoha aplikací domácí automatizace, IoT nebo bezdrátových ovladačů.
Kompletní program, který si ještě následně rozebereme:
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// GPIO vestavěné LED (většina ESP32 desek = GPIO2)
const int LED_PIN = 2;
// UUID stejné jako v MicroPythonu
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
BLEServer* pServer;
BLECharacteristic* pCharacteristic;
// --------------------------------------------------
// Callback pro připojení / odpojení
// --------------------------------------------------
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("Zařízení připojeno");
}
void onDisconnect(BLEServer* pServer) {
Serial.println("Zařízení odpojeno, spouštím advertising znovu...");
// Po odpojení začne ESP32 znovu vysílat
BLEDevice::startAdvertising();
}
};
// --------------------------------------------------
// Callback pro zápis do charakteristiky
// --------------------------------------------------
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pCharacteristic) {
String value = pCharacteristic->getValue();
if (value == "1") {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED zapnuta");
} else {
digitalWrite(LED_PIN, LOW);
Serial.println("LED vypnuta");
}
}
};
void setup() {
Serial.begin(115200);
// Inicializace LED
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Inicializace BLE a jméno zařízení
BLEDevice::init("ESP32-LED");
// Vytvoření BLE serveru
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Vytvoření služby
BLEService* pService = pServer->createService(SERVICE_UUID);
// Vytvoření zapisovatelné charakteristiky
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
// Výchozí hodnota = LED vypnuta
pCharacteristic->setValue("0");
// Spuštění služby
pService->start();
// Advertising
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Serial.println("Čekám na připojení a vysílám jméno ESP32-LED...");
}
void loop() {
// Vše obsluhují BLE callbacky
delay(1000);
}
Jak program funguje
Základní struktura programu je stejná jako u ukázkového příkladu BLE_server, který jsme si rozebrali v předchozí části článku. ESP32 tedy opět vytváří BLE server, službu a charakteristiku a po spuštění začne své služby inzerovat (advertising), aby je mohl najít chytrý telefon.
V tomto příkladu jsou však důležité především tři nové části:
- ovládání vestavěné LED,
- reakce na připojení a odpojení klienta,
- zpracování dat zapsaných z telefonu do BLE charakteristiky.
Nastavení pinu pro LED
Na začátku programu je definován pin, na kterém je připojena vestavěná LED vývojové desky.
const int LED_PIN = 2;
U většiny vývojových desek ESP32 je vestavěná LED připojena na GPIO2. Pokud používáte jinou desku, může být LED připojena na jiný pin.
V proceduře setup() je pin nastaven jako výstup a LED je pro jistotu zhasnuta:
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
UUID služby a charakteristiky
Stejně jako v předchozím příkladu používáme vlastní UUID služby a charakteristiky:
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
Význam UUID jsme si vysvětlili v části věnované GATT a BLE serveru, takže zde je nebudeme znovu podrobně rozebírat. Důležité je pouze to, že telefon musí komunikovat se stejnou službou a charakteristikou, které zde vytváří ESP32.
Reakce na připojení a odpojení telefonu
Novou částí programu je třída MyServer, která reaguje na události připojení a odpojení BLE klienta.
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("Zařízení připojeno");
}
void onDisconnect(BLEServer* pServer) {
Serial.println("Zařízení odpojeno, spouštím advertising znovu...");
BLEDevice::startAdvertising();
}
};
Po připojení telefonu se pouze vypíše informace na sériový monitor.
Důležitější je metoda onDisconnect(). Po odpojení klienta znovu spustí advertising, takže je ESP32 opět viditelné pro další připojení. Bez tohoto kroku by po odpojení telefonu zůstalo zařízení nepřístupné, dokud bychom jej nerestartovali.
Zpracování dat z telefonu
Nejdůležitější část programu představuje třída MyCallbacks. Ta reaguje na zápis do BLE charakteristiky.
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pCharacteristic) {
String value = pCharacteristic->getValue();
Metoda getValue() načte hodnotu, kterou telefon zapsal do charakteristiky.
Následuje jednoduché vyhodnocení přijaté hodnoty:
if (value == "1") {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED zapnuta");
} else {
digitalWrite(LED_PIN, LOW);
Serial.println("LED vypnuta");
}
Pokud telefon pošle znak „1“, LED se rozsvítí. Jakákoli jiná hodnota (v našem případě typicky „0“) LED zhasne.
Oproti ukázkovému programu BLE_server zde místo pouhého uchování textové hodnoty program provádí reálnou akci nad hardwarem.
Vytvoření zapisovatelné charakteristiky
Charakteristika je vytvořena pouze s vlastností WRITE:
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_WRITE
);
Na rozdíl od ukázkového programu zde nepotřebujeme vlastnost READ, protože telefon nebude hodnotu číst, ale pouze do ní zapisovat příkazy pro ovládání LED.
Na tuto charakteristiku navážeme naši callback třídu:
pCharacteristic->setCallbacks(new MyCallbacks());
Tím zajistíme, že se metoda onWrite() automaticky zavolá při každém zápisu z telefonu.
Spuštění služby a advertising
Následující část je prakticky stejná jako u dříve rozebraného příkladu BLE serveru:
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Podrobný význam těchto kroků jsme si vysvětlili v části Modul ESP32 jako BLE server, proto jej zde již neopakujeme.
Možná vás překvapí, že hlavní smyčka programu loop() neobsahuje žádný kód pro ovládání LED. Je to proto, že celý program je řízen událostmi (callbacks). BLE knihovna sama sleduje připojení klienta i zápisy do charakteristiky a v případě potřeby zavolá příslušnou metodu (onConnect, onDisconnect, onWrite). Tento způsob programování je pro BLE velmi typický a umožňuje vytvářet přehledné programy, ve kterých hlavní smyčka nemusí neustále kontrolovat stav komunikace.
Na animaci (obrázek 11) vidíme způsob, jak můžeme výše uvedený program otestovat pomocí aplikace nRF Connect:
PŘÍKLAD č. 2: Program – načítání tlačítka
V předchozím příkladu jsme pomocí telefonu ovládali LED na modulu ESP32. Komunikace tedy probíhala směrem telefon → ESP32 a telefon do BLE charakteristiky zapisoval příkazy. Nyní si ukážeme opačný směr komunikace, který je také v praxi velmi častý. Modul ESP32 bude sledovat stav vestavěného tlačítka BOOT a při každé změně jeho stavu odešle informaci do telefonu pomocí BLE notifikace (Notify). Telefon tedy nebude hodnotu opakovaně číst, ale ESP32 mu ji samo aktivně odešle ve chvíli, kdy dojde ke změně stavu tlačítka.
Na tomto příkladu si ukážeme zejména:
- vytvoření charakteristiky s vlastností
NOTIFY, - použití deskriptoru
BLE2902, - odeslání dat do telefonu pomocí metody
notify(), - sledování změny stavu vstupního pinu.
Všechny ostatní části programu (vytvoření BLE serveru, služby, advertising a obsluha připojení klienta) jsou stejné nebo velmi podobné předchozím příkladům, proto je již nebudeme podrobně rozebírat.
Kompletní kód programu, který si pak opět popíšeme:
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define BUTTON_PIN 0
#define SERVICE_UUID "a1b2c300-1234-5678-9abc-def012345678"
#define CHARACTERISTIC_UUID "a1b2c301-1234-5678-9abc-def012345678"
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
bool lastState;
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Připojeno");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Odpojeno");
pServer->startAdvertising();
}
};
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
lastState = digitalRead(BUTTON_PIN);
BLEDevice::init("ESP32-BUTTON");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->addDescriptor(new BLE2902());
pService->start();
// Advertising
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Serial.println("Čekám na připojení a vysílám jméno ESP32-BUTTON...");
}
void loop() {
bool state = digitalRead(BUTTON_PIN);
if (state != lastState) {
lastState = state;
uint8_t value = (state == LOW) ? 1 : 0;
if (deviceConnected) {
pCharacteristic->setValue(&value, 1);
pCharacteristic->notify();
Serial.print("Odesláno: ");
Serial.println(value);
}
}
delay(20);
}
Jak program funguje
Nastavení tlačítka BOOT
Na začátku programu je definován pin tlačítka:
#define BUTTON_PIN 0
U většiny vývojových desek ESP32 je tlačítko BOOT připojeno na GPIO0.
V proceduře setup() je pin nastaven jako vstup s interním pull-up rezistorem:
pinMode(BUTTON_PIN, INPUT_PULLUP);
To znamená, že v klidovém stavu je na pinu logická 1 (HIGH), po stisknutí tlačítka se pin spojí se zemí a čteme logickou 0 (LOW).
Počáteční stav tlačítka si uložíme do proměnné lastState, pomocí které pak vyhodnocujeme změnu stavu (viz dále):
lastState = digitalRead(BUTTON_PIN);
Charakteristika s vlastností NOTIFY
Nejdůležitější změnou oproti prvnímu příkladu je vytvoření charakteristiky s vlastností NOTIFY:
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
V předchozím příkladu jsme použili vlastnost WRITE, protože telefon posílal data do ESP32. Zde je situace opačná – ESP32 bude posílat data telefonu. Klient (například aplikace nRF Connect) se může k notifikacím přihlásit a poté dostává nové hodnoty automaticky, bez nutnosti je neustále číst.
Deskriptor BLE2902
Za charakteristiku přidáme ještě deskriptor:
pCharacteristic->addDescriptor(new BLE2902());
Tento deskriptor je pro notifikace velmi důležitý. Umožňuje klientovi (telefonu) zapnout nebo vypnout příjem notifikací. Bez něj by většina BLE aplikací možnost Notify vůbec nenabídla.
Sledování připojení klienta
Třída MyServer je podobná jako v předchozím příkladu:
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Připojeno");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Odpojeno");
pServer->startAdvertising();
}
};
Proměnná deviceConnected slouží k tomu, abychom odesílali notifikace pouze tehdy, když je telefon skutečně připojen. Po odpojení klienta se znovu spustí advertising, takže je možné se znovu připojit bez restartu modulu.
Detekce změny stavu tlačítka
V hlavní smyčce programu loop() načítáme aktuální stav tlačítka:
bool state = digitalRead(BUTTON_PIN);
Následuje porovnání s předchozím stavem:
if (state != lastState) {
lastState = state;
Díky tomu reagujeme pouze na změnu stavu a neposíláme data neustále dokola.
Stisknuté tlačítko (LOW) převedeme na hodnotu 1, uvolněné tlačítko (HIGH) na 0:
uint8_t value = (state == LOW) ? 1 : 0;
Používáme datový typ uint8_t, tedy jeden bajt, protože notifikace obvykle odesílají binární data.
Odeslání notifikace
Vlastní odeslání proběhne pouze tehdy, když je připojen klient:
if (deviceConnected) {
pCharacteristic->setValue(&value, 1);
pCharacteristic->notify();
}
Tato část je klíčová:
setValue(– uloží do charakteristiky jeden bajt s hodnotou&value, 1) 0nebo1,notify()– okamžitě odešle novou hodnotu všem přihlášeným klientům.
Právě metoda notify() představuje hlavní rozdíl oproti prvnímu příkladu. ESP32 zde nečeká, až si telefon hodnotu přečte, ale aktivně mu ji pošle. Pro kontrolu programu vypisujeme odeslanou hodnotu i na sériový monitor:
Serial.print("Odesláno: ");
Serial.println(value);
Při stisku tlačítka tedy uvidíme:Odesláno: 1, po uvolnění tlačítka: Odesláno: 0.
Na konci smyčky je malé zpoždění delay(20);, které slouží jako jednoduché omezení rychlosti čtení tlačítka a zároveň částečně pomáhá potlačit zákmit kontaktů (debounce). Pro jednoduchý demonstrační příklad je tato hodnota dostačující.
Na animaci (obrázek 12) opět vidíme způsob, jak můžeme program otestovat pomocí aplikace nRF Connect:
Jak příklady otestovat
Oba uvedené programy lze velmi snadno otestovat pomocí aplikace nRF Connect, kterou jsme používali již při vysvětlování ukázkových BLE programů. Postup připojení, zobrazení služeb a charakteristik i práce se zápisem a notifikacemi jsme si ukázali v předchozí části článku a jsou naznačeny také v doprovodných animacích.
Kromě aplikace nRF Connect však můžeme využít i modernější a uživatelsky přívětivější přístup – Web Bluetooth. Tomuto tématu se podrobně věnujeme v samostatném článku: Ovládání přes Web Bluetooth. Přestože je tento článek zařazen v sekci věnované Bluetooth programovanému v MicroPythonu, není to pro naše testování podstatné. Technologie Web Bluetooth pracuje na úrovni BLE služeb a charakteristik, takže stejným způsobem funguje i s modulem ESP32 naprogramovaným v prostředí Arduino IDE. Důležité pouze je, aby odpovídaly použité UUID služby a charakteristiky.
Na stránce článku o Web Bluetooth jsou navíc vloženy dvě jednoduché webové aplikace určené právě pro ovládání LED a sledování stavu tlačítka. Praktické příklady v této části článku jsou záměrně napsány tak, aby s těmito webovými stránkami přímo spolupracovaly. Článek o Web Bluetooth tedy můžete použít jako hotovou testovací stránku pro oba zde uvedené programy.
Tento způsob testování má i další výhodu: seznámí nás s technikou Web Bluetooth, která umožňuje ovládat zařízení ESP32 přes Bluetooth mnohem pohodlněji než pouze pomocí aplikace nRF Connect. Díky HTML stránkám můžeme vytvořit vlastní grafické rozhraní, tlačítka, indikátory nebo celý uživatelsky přívětivý ovládací panel pro domácí automatizaci a další IoT aplikace.
Závěr
Pro dnešek se asi shodneme, že už toho Bluetooth bylo dost! Vyzkoušeli jsme si BLE server, skenování okolních zařízení, ovládání LED z telefonu i odesílání notifikací z tlačítka BOOT. Na jednoduché bezdrátové projekty je to víc než slušný základ. Pokud vám při testování blikala LED, chodily notifikace a telefon poslušně komunikoval s ESP32, pak gratulujeme – právě jste udělali první krok od „ukázkových příkladů“ k vlastním BLE aplikacím. A to je přesně ten okamžik, kdy se z teorie začíná stávat skutečné bastlení! 🤓
Příště si ale od Bluetooth na chvíli odpočineme a zase se sejdeme u nějakého „bastlířsky přívětivějšího“ tématu.