Stránka 1 z 1

[sw] BLE komunikace mobil -> ESP32

Napsal: 12 říj 2024, 16:19
od Pablo74
Mám vývojovou desku s ESP32 a displejem, konkrétně tuhle: https://www.aliexpress.com/item/1005006269242344.html

Většina čipů ESP32 (až na výjimky) podporuje Bluetooth a BLE = Bluetooth Low Energy.

Udělal jsem malý projekt, který zahrnuje tuto vývojovou desku a mobil či noťas s jednosměrnou BLE komunikací. Posílám si text z mobilu / noťasu na ESP32, kde se zobrazí na displeji.

Přestože jsou na webu i Youtube vzorová řešení BLE komunikace, narazil jsem na pár problémů, které už byly nad moje znalosti. Řešení mi našla AI, konkrétně Cloude 3.5 Sonet a GPT-4o.

Na ESP32 mi běží kód psaný ve Wiringu, který vypadá takhle:

Kód: Vybrat vše

#include <Arduino.h>

// display libraries
#include <Wire.h>
#include <U8g2lib.h>

// BLE libraries
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// display init
U8G2_SSD1306_128X64_NONAME_F_SW_I2C
u8g2(U8G2_R0, 22, 21, U8X8_PIN_NONE);

// BLE
BLEServer* pServer = NULL;
BLECharacteristic* pTextCharacteristic = NULL;

bool deviceConnected = false;

#define SERVICE_UUID "abcdef00-1234-5678-9876-abcdefabcdef"
#define CHARACTERISTIC_UUID_TEXT "abcdef02-1234-5678-9876-abcdefabcdef"

// Display text string "BLE connection" on the top of the screen
void displayConnText(){
  u8g2.clearBuffer();
  // name of font ends with _tr -> no diacritics charachters like ěščřžýáíé 
  u8g2.setFont(u8g2_font_7x14B_tr);
  u8g2.drawStr(0, 12, "BLE connection: ");
  u8g2.drawLine(0, 15, 127, 15);
  u8g2.sendBuffer();
}

void displayConnStatus(bool status){
  if (status) {
    u8g2.drawStr(112, 12, "Y");
  }
  else {
    u8g2.drawStr(112, 12, "N");
  }
  u8g2.sendBuffer();
}

// BLE callbacks
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    displayConnStatus(deviceConnected);
  };
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    displayConnStatus(deviceConnected);
    pServer->getAdvertising()->start();  // Restart advertising
  }
};

class TextCallback: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    String received = pTextCharacteristic->getValue();
    if (received.length() > 0) {
      Serial.print("Received text: ");
      Serial.println(received);

      if (received == "!"){
        u8g2.clear();
        displayConnText();
        displayConnStatus(deviceConnected);
      }
      else {
        // set color to background (0) 
        u8g2.setDrawColor(0);
        u8g2.drawBox(0, 40, 128, 24);
        // set color to foreground (1)
        u8g2.setDrawColor(1);
        u8g2.drawStr(0, 60, received.c_str());
      }
      u8g2.sendBuffer();
    }
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
 
  u8g2.begin();
  displayConnText();
  displayConnStatus(deviceConnected);

  // device name
  BLEDevice::init("ESP32A");
  Serial.println("BLE Device initialized");

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

  BLEService *pService = pServer->createService(SERVICE_UUID);
  pTextCharacteristic = pService->createCharacteristic(
                            CHARACTERISTIC_UUID_TEXT,
                            BLECharacteristic::PROPERTY_WRITE
                          );
  pTextCharacteristic->setCallbacks(new TextCallback());

  pService->start();

  // Advertising
  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!");
  Serial.print("Device MAC: ");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {

}
Na mobilu / noťasu mi běží webová aplikace (HTML + CSS + JS); nepoužil jsem vanilla („čistý“) JS, ale méně známý framework MithrilJS.
S využitím AI není problém si tento kód nechat přepsat třeba do VueJS či vanilla („čistého“) JS. Vypadá takhle:

Kód: Vybrat vše

var stream = window.m.stream;

var connected = stream(false);
var statusText = stream("not connected");
var deviceText = stream("(none)");

const serviceUUID = "abcdef00-1234-5678-9876-abcdefabcdef";
const textCharacteristicUUID = "abcdef02-1234-5678-9876-abcdefabcdef";

var device;
var server;
var service;
var textCharacteristic;

async function connect() {
  try {
    device = await navigator.bluetooth.requestDevice({
      /*
      filters: [{ services: [serviceUUID] }]
      */
      acceptAllDevices: true,
      optionalServices: [serviceUUID]
    });

    server = await device.gatt.connect();
    service = await server.getPrimaryService(serviceUUID);

    textCharacteristic = await service.getCharacteristic(textCharacteristicUUID);

    connected(true);
    statusText("connected");
    deviceText(device.name);
    m.redraw();
  } catch (error) {
    console.log(error);
  }
}

/*
function disconnect(){
  device.gatt.disconnect();

  connected(false);
  statusText("not connected");
  deviceText("(none)");
}
*/

function disconnect() {
  if (device && device.gatt.connected) {
    device.gatt.disconnect();
  }

  connected(false);
  statusText("not connected");
  deviceText("(none)");
  device = null;
  server = null;
  service = null;
  textCharacteristic = null;
}


async function sendText(){
  var text = document.getElementById('input').value;
  var encoder = new TextEncoder();
  await textCharacteristic.writeValue(encoder.encode(text));
  document.getElementById('input').value = "";
}

async function sendTextClearDisplay(){
  var text = "!";
  var encoder = new TextEncoder();
  await textCharacteristic.writeValue(encoder.encode(text)); 
}

var header = {
  view: function(vnode){
    return m("header", {class: "q-header q-layout__section--marginal fixed-top", style: {"background-color":"#FF9900", color:"white"}}, 
      [
        m("div", {class: "q-toolbar row no-wrap items-center", role: "toolbar"}, 
          m("div", {class: "q-toolbar__title ellipsis"}, 
            m("div", {class: "text-big absolute-center text-bold"}, 
              vnode.attrs.title
            )
          )
        ), 
      ]
    )
  }
}

var footer = {
  view: function(vnode){
    return m("footer", {class: "q-footer q-layout__section--marginal fixed-bottom q-px-none", style: {"background-color": "#FF9900", color: "white"}}, 
      m("div", {class: "q-toolbar row no-wrap ", role: "toolbar"}, 
        m("div", {class: "q-toolbar__title ellipsis"}, 
          m("div", {class: "text-big absolute-center text-bold "}, 
            vnode.attrs.title
          )
        )
      )
    )
  }
}

var pageContent = {
  view: function(){
    return m("div", {class: "q-layout q-layout--standard", tabindex: "-1"}, 
      m("div", {class: "q-page-container q-pa-none", style: {"padding-top":"50px", "padding-bottom": "50px"}}, 
        m("div", {class: "q-my-md q-px-sm"}, 
          m("div", {class: !connected() ? "text-italic q-my-md" : "q-my-md"}, "device: " + deviceText()),
          m("div", {class: "q-my-md"}, "status: " + statusText()),
          !connected()
          ? // NOT CONNECTED
          m("button", {onclick: connect, class: "q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle q-btn--actionable q-focusable q-hoverable q-btn--no-uppercase btn-roll q-btn__content text-center items-center q-anchor--skip justify-center row q-focus-helper full-width back-blue text-white", }, "connect with device")
          : // CONNECTED
          m("div", [      // begin of div -> connected()
            m("button", {onclick: disconnect, class: "q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle q-btn--actionable q-focusable q-hoverable q-btn--no-uppercase btn-roll q-btn__content text-center items-center q-anchor--skip justify-center row q-focus-helper full-width back-black text-white", }, "disconnect from device"),
            m("input", {id: "input", style: "width: 100%; margin-top: 1rem; margin-bottom: 1rem; padding: 0.5rem"}),
            m("div", {class: "row"},
              m("div", {class: "col q-mr-xs"},
                m("button", {onclick: function(){sendTextClearDisplay()}, class: "q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle q-btn--actionable q-focusable q-hoverable q-btn--no-uppercase btn-roll q-btn__content text-center items-center q-anchor--skip justify-center row q-focus-helper full-width back-magenta text-white", }, "clear display")
              ),
              m("div", {class: "col q-ml-xs"}, 
                m("button", {onclick: function(){sendText()}, class: "q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle q-btn--actionable q-focusable q-hoverable q-btn--no-uppercase btn-roll q-btn__content text-center items-center q-anchor--skip justify-center row q-focus-helper full-width back-green text-white", }, "send text")
              ),
            ),
          ])            // end of div -> connected()
        )
      )
    )
  }
}

var app = {
  view: function(){
    return [
      m(header, {title: "BLE Ideaspark communication"}), 
      m(pageContent), 
      m(footer, {class: "", title: "(c) 2024 Ing. Pavel Kříž"})
    ]
  }
}

m.mount(document.body, app);
Ptáte na praktické využití? Je to první krok k tomu, jak přes BLE ovládat ESP32 a ten může ovládat další zařízení. Zatím tedy jen zobrazuje text, ale na základě tohoto funkčního kódu už není problém udělat komplexnější program, kterým půjdou ovládat jednotlivé piny ESP32 a z nich další zařízení.

Ať je vám kód inspirací a k užitku!