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() {
}
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);
Ať je vám kód inspirací a k užitku!