ESP32 Bluetooth Server

Dneska se podíváme jak zprovoznit Bluetooth na ESP32. Cílem je umožnit telefonu připojit se na naše Bluetooth zařízení a odpovídat na specifické zprávy. Jako vývojové prostředí použijeme PlatformIO.

ESP32

Pro projekt použijeme ESP32 Development Board z Aliexpressu. V době psaní článku stojí zhruba 120CZK.

platformio.ini

Konfigurační soubor pro projekt.

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

main.cpp

Jako základ kódu jsem použil výborný článek ESP32 a Bluetooth Low Energy (BLE) z Drátek Návody a provedl úpravy pro potřeby projektu. Pro zjednodušení nejprve celý kód projektu. Rozbor bude pokračovat.

#include <Arduino.h>

// Bluetooth knihovny
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// inicializace modulu z knihovny
BLECharacteristic *pCharacteristic;

// proměnná pro kontrolu připojených zařízení
bool deviceConnected = false;

// proměnná pro ukládání přijaté zprávy
std::string receivedMessage;

// proměnná pro zprávu pro odeslání
String out = "";

// definice unikátních ID pro různé služby,
// pro vlastní UUID využijte generátor
// https://www.uuidgenerator.net/
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

int compareCommand(std::string message, String command)
{
    String converted = String(message.c_str());
    converted.trim();
    converted.toLowerCase();

    return converted == command;
}

void setOutputMessage(String message)
{
    out = message;
    out += "\n";
}

void clearOutputMessage()
{
    out = "";
}

void sendMessage()
{
    char message[out.length() + 1];
    out.toCharArray(message, out.length() + 1);

    // přepsání zprávy do BLE služby
    pCharacteristic->setValue(message);

    // odeslání zprávy skrze BLE do připojeného zařízení
    pCharacteristic->notify();
}

// třída pro kontrolu připojení
class MyServerCallbacks : public BLEServerCallbacks
{
    // při spojení zařízení nastav proměnnou na log1
    void onConnect(BLEServer *pServer)
    {
        deviceConnected = true;
    };
    // při odpojení zařízení nastav proměnnou na log0
    void onDisconnect(BLEServer *pServer)
    {
        deviceConnected = false;
        pServer->getAdvertising()->start();
    }
};

// třída pro příjem zprávy
class MyCallbacks : public BLECharacteristicCallbacks
{
    // při příjmu zprávy proveď následující
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        // načti přijatou zprávu do proměnné
        receivedMessage = pCharacteristic->getValue();

        // pokud není zpráva prázdná, vypiš její obsah
        // po znacích po sériové lince
        // a pokud přijde očekávaný příkaz pošli odpověď
        if (receivedMessage.length() > 0)
        {
            Serial.print("Prijata zprava: ");
            for (int i = 0; i < receivedMessage.length(); i++)
            {
                Serial.print(receivedMessage[i]);
            }
            Serial.println();

            if (compareCommand(receivedMessage, "ahoj"))
            {
                setOutputMessage("No nazdar");
            }
        }
    }
};

void setup()
{
    // zahájení komunikace po sériové lince
    // rychlostí 115200 baud
    Serial.begin(115200);

    // inicializace Bluetooth s nastavením jména zařízení
    BLEDevice::init("ESP32 BLE");

    // vytvoření BLE serveru
    BLEServer *pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // vytvoření BLE služby
    BLEService *pService = pServer->createService(SERVICE_UUID);

    // vytvoření BLE komunikačního kanálu pro odesílání (TX)
    pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY);
    pCharacteristic->addDescriptor(new BLE2902());

    // vytvoření BLE komunikačního kanálu pro příjem (RX)
    BLECharacteristic *pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE);
    pCharacteristic->setCallbacks(new MyCallbacks());

    // zahájení BLE služby
    pService->start();

    // zapnutí viditelnosti BLE
    pServer->getAdvertising()->start();
    Serial.println("BLE nastaveno, ceka na pripojeni..");
}

void loop()
{
    if (deviceConnected == true && out.length() > 0)
    {
        sendMessage();
        delay(100);
        clearOutputMessage();
    }

    // pauza před novým během smyčky
    delay(1000);
}

Rozbor kódu

#include <Arduino.h>

// Bluetooth knihovny
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

Knihovny potřebné pro obsluhu Bluetooth.

BLECharacteristic *pCharacteristic;

Inicializace modulu knihovny pro práci s Bluetooth.

bool deviceConnected = false;

Indikace, že je zařízení připojeno k našemu Bluetooth zařízení.

std::string receivedMessage;

Proměnná pro uložení zprávy, kterou pošleme našemu Bluetooth zařízení.

String out = "";

Proměnná pro uložení odchozí zprávy.

#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

Bluetooth se skládá z jednotlivých služeb pro které je mít potřeba vygenerované unikátní identifikátory.

int compareCommand(std::string message, String command)
{
    String converted = String(message.c_str());
    converted.trim();
    converted.toLowerCase();

    return converted == command;
}

Naše Bluetooth zařízení se chová jako server. Pokud mu pošlete zprávu/příkaz, který rozpozná, tak odpoví zpět. Příchozí zpráva se ukládá do proměnné typu std::string. Zprávu je potřeba očistit od bílých znaků, normalizovat (převést na malá písmena 🙂 ) a porovnat s příkazem, který chceme provést. Tady byl lehce zádrhel, protože vhodné metody obsahuje zase String objekt. Céčko/C++ jsem provozoval naposledy na vysoké pro školní projekty a už tam jsem většinu věcí řešil v Pythonu. Jsem už prostě zmlsaný moderními programovacími jazyky. 😀 Mé řešení asi nebude nejefektivnější, ale je lehce čitelné a funguje.

TLDR: compareCommand vezme příchozí zprávu, očistí od bordelu, porovná s námi zadaným příkazem a vrátí hodnotu porovnání.

void setOutputMessage(String message)
{
    out = message;
    out += "\n";
}

Pomocná funkce pro nastavení odchozí zprávy.

void clearOutputMessage()
{
    out = "";
}

Pomocná funkce pro vyčistění proměnné po odeslání zprávy.

void sendMessage()
{
    char message[out.length() + 1];
    out.toCharArray(message, out.length() + 1);

    pCharacteristic->setValue(message);

    pCharacteristic->notify();
}

Funkce zapouzdřující odeslání zprávy. Převede zprávu na pole znaků, předá Bluetooth knihovně a pomocí metody notify odešle na připojené zařízení.

class MyServerCallbacks : public BLEServerCallbacks
{
    void onConnect(BLEServer *pServer)
    {
        zarizeniPripojeno = true;
    };

    void onDisconnect(BLEServer *pServer)
    {
        zarizeniPripojeno = false;
        pServer->getAdvertising()->start();
    }
};

Během testů se objevil problém s opětovným připojením. Když se telefon odpojil od Bluetooth serveru, nešlo se znovu připojit. Pomohl jen restart ESPčka. Dle různých zdrojů za to můžou různé verze knihoven. Pokud přidáme při odpojení zařízení pServer->getAdvertising()->start();, který začne propagovat náš Bluetooth server do prostoru, opětovné připojení funguje.

Třída zapouzdřující callbacky pro náš Bluetooth server. Pokud se zařízení připojí/odpojí nastaví proměnnou deviceConnected.

class MyCallbacks : public BLECharacteristicCallbacks
{
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        receivedMessage = pCharacteristic->getValue();

        if (receivedMessage.length() > 0)
        {
            Serial.print("Prijata zprava: ");
            for (int i = 0; i < receivedMessage.length(); i++)
            {
                Serial.print(receivedMessage[i]);
            }
            Serial.println();

            if (compareCommand(receivedMessage, "ahoj"))
            {
                setOutputMessage("No nazdar");
            }
        }
    }
};

Třída zapouzdřující callback pro příjem zprávy. Při příchozí zprávě nenulové délky se jako první vypíše do sériové konzole. Následně proběhne porovnání jestli obsahuje, pro nás známý, příkaz. V našem případě ahoj. Pokud ano, tak odpoví No nazdar.

setup()

Serial.begin(115200);

Nastavení rychlostí komunikace přes sériovou linku. Musí být stejné jako v platformio.ini.

BLEDevice::init("ESP32 BLE");

Inicializace Bluetooth s nastavením jména, na které se bude dát z telefonu připojit.

BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());

Vytvoření Bluetooth serveru. Nastavíme callbacky pro detekci připojení/odpojení zařízení pomocí třídy nadefinované výše.

BLEService *pService = pServer->createService(SERVICE_UUID);

Vytvoření Bluetooth služby.

pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY);
pCharacteristic->addDescriptor(new BLE2902());

Vytvoření komunikačního kanálu pro odesílání zpráv.

BLECharacteristic *pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setCallbacks(new MyCallbacks());

Vytvoření kanálu pro příjem zpráv. Nastavujeme callback pro příjem nadefinovaný výše.

pService->start();

Nastartování Bluetooth služby.

pServer->getAdvertising()->start();
Serial.println("BLE nastaveno, ceka na pripojeni..");

Zviditelnění pro ostatní zařízení.

loop()

if (deviceConnected == true && out.length() > 0)
{
    sendMessage();
    delay(100);
    clearOutputMessage();
}

delay(1000);

Loop smyčka každou sekundu zkontroluje jestli je zařízení připojené k našemu Bluetooth serveru a jestli existuje zpráva co by šla odeslat. Pokud jsou obě podmínky splněny odešleme zprávu a vyčistíme data.

TLDR

Bluetooth server čeká na zp rávy. Pokud přijde zpráva, která obsahuje známy příkaz, nastaví se příslušné proměnné. Smyčka loop každou sekundu zkontroluje jestli jsou příslušné proměnné pro odchozí zprávu a pokud ano odešle zprávu na připojené zařízení. No raketová věda to teda není. 😀

Serial Bluetooth Terminal

Kód je zkompilovaný, nahraný na ESP32 a běží. Teď je potřeba začít s naším Bluetooth zařízením komunikovat. Pro Android jde použít výbornou aplikaci Serial Bluetooth Terminal. Umí to co potřebujeme. Připojit se k zařízení, posílat a obdržet zprávy.

Po instalaci se v obrazovce Devices přepněte do tabu Bluetooth LE a naskenujte zařízení. Objeví se naše ESPčko. Kliknutím na něj se připojíte.

Pokud připojení proběhne v pořádku zobrazí se následující.

Po odeslání zprávy ahoj dostaneme odpověď.

Závěr

Prezentovaný kód je takové minimum, bez hlubší znalosti Bluetooth a jeho vnitřního fungování, které ověřuje koncept ESP32 jako Bluetooth serveru odpovídajícího na příkazy.

Happy coding.

Loading